Concepts / Building Search UI / Server side rendering
May. 10, 2019

Server Side Rendering

You are reading the documentation for Angular InstantSearch v3, which is in beta. You can find the v2 documentation here.

Overview

This is an advanced guide. If you’ve never used Angular InstantSearch, you should follow the getting-started first.

You can find the result of this guide on the Angular InstantSearch repository.

Angular InstantSearch is compatible with server-side rendering with Angular version 5. We provide an API that is easy to use with @angular/universal modules.

For simplicity we are going to use the @angular/universal-starter boilerplate which is a minimal Angular starter for Universal JavaScript using TypeScript and Webpack.

Angular Universal is not working completely with version 6 and 7, you will need to checkout a specific commit of universal-start boilerplate.

How it works?

The server-side rendering uses two concepts from @angular/universal modules:

  • TransferState: Will cache the first request made to Algolia from your server in order to avoid replicating it when the Angular application starts on the client side.
  • preboot: Will avoid the first rendering of your Angular application on the client side and will start it from the HTML markup sent by the server.

In order to assemble all the pieces you will need to write some code in your own application and instantiate Angular InstantSearch. Let’s dive into the code!

Setup

First, clone the @angular/universal-starter boilerplate:

1
2
3
4
> git clone git@github.com:angular/universal-starter.git [your-app-name]
> git reset --hard 02758f80501b18b9f49834e367136bd9590ccc04 # Angular 5
> cd [your-app-name]
> yarn

The next step is to install preboot and angular-instantsearch packages as well:

1
> yarn add preboot angular-instantsearch@beta

Now you have all the requirements to start developing your universal Angular InstantSearch application!

1. Angular Universal modules

Once you’ve installed the dependencies you will need to add the TransferState, preboot and HttpClient modules into src/app/app.module.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import {
  BrowserModule,
  BrowserTransferStateModule
} from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HttpClientModule } from "@angular/common/http";

import { PrebootModule } from 'preboot';

import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
  ],
  imports: [
    BrowserModule.withServerTransition({appId: 'my-app'}),
    RouterModule.forRoot([
      { path: '', component: HomeComponent, pathMatch: 'full'},
      { path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule'},
      { path: 'lazy/nested', loadChildren: './lazy/lazy.module#LazyModule'}
    ]),
    PrebootModule.withConfig({ appRoot: "app-root" }),
    BrowserTransferStateModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We need also to import ServerTransferStateModule into src/app/app.server.module.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { NgModule } from '@angular/core';
import {
  ServerModule,
  ServerTransferStateModule // THIS
} from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ServerTransferStateModule, // <- AND THIS
    ModuleMapLoaderModule,
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

And voilà, you have the requirements and your are now ready to plug Angular InstantSearch into your universal Angular application!

2. Transfer the search query to your server

In order to get the query of the client request into your Angular application you need to provide the original request object you receive into the express server. Open ./server.ts and replace this block:

1
2
3
4
5
6
app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

By this one:

1
2
3
4
5
6
7
8
9
10
app.engine('html', (_, options, callback) => {
  const engine = ngExpressEngine({
    bootstrap: AppServerModuleNgFactory,
    providers: [
      { provide: 'request', useFactory: () => options.req, deps: [] },
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  });
  engine(_, options, callback);
});

Now on server-side rendering we can have access to the request object by using the injector. We will see how to do that in the next chapter.

3. Angular InstantSearch

First, you need to import the Angular InstantSearch module into your application like you will do in any Angular application. (If you don’t know how to do this, please read the following part in the getting started guide).

The only difference is on how you configure <ais-instantsearch> component.

This will be our starting component. For simplicity you can re-use the Home component from the universal starter boilerplate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Component } from '@angular/core';

@Component({
  selector: 'home',
  template: `
    <ais-instantsearch [config]="instantSearchConfig">
    </ais-instantsearch>
  `
})
export class HomeComponent {
  public instantSearchConfig: {};

  constructor() {
    this.instantSearchConfig = {
      appId: "latency",
      apiKey: "6be0576ff61c053d5f9a3225e2a90f76",
      indexName: "instant_search",
      urlSync: true
    }
  }
}

We will need to now import the TransferState, HttpClient, Injector and PLATFORM_ID into our constructor, let’s update our component code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Component, Injector, Inject, PLATFORM_ID } from "@angular/core";
import { isPlatformServer } from "@angular/common";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { TransferState, makeStateKey } from "@angular/platform-browser";

@Component({
  selector: 'home',
  template: `
    <ais-instantsearch [config]="instantSearchConfig">
    </ais-instantsearch>
  `
})
export class HomeComponent {
  public instantSearchConfig: {};

  constructor(
    private httpClient: HttpClient,
    private transferState: TransferState,
    private injector: Injector,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {
    this.instantSearchConfig = {
      appId: "latency",
      apiKey: "6be0576ff61c053d5f9a3225e2a90f76",
      indexName: "instant_search",
      urlSync: true
    }
  }
}

Final step is to update the instantSearchConfig with the modules we provide into angular-instantsearch in order to allow the Algolia API requests to be made on the server side:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import {
  createSSRSearchClient,
  parseServerRequest
} from "angular-instantsearch";

[...]

constructor(
  private httpClient: HttpClient,
  private transferState: TransferState,
  private injector: Injector,
  @Inject(PLATFORM_ID) private platformId: Object
) {
  const req = isPlatformServer(this.platformId)
    ? this.injector.get("request")
    : undefined;

  const searchParameters = parseServerRequest(req);

  this.instantSearchConfig = {
    searchParameters,
    indexName: "instant_search",
    urlSync: true,
    searchClient: createSSRSearchClient({
      makeStateKey,
      HttpHeaders,
      transferState: this.transferState,
      httpClient: this.httpClient,
      appId: "latency",
      apiKey: "6be0576ff61c053d5f9a3225e2a90f76"
    })
  };
}

You cannot use routing: true option instead of urlSync: true yet, there is an issue with Preboot that will randomly render a blank page.

4. Wrapping up

Congratulations! You can now add more Angular InstantSearch widgets on your search page component and run:

1
2
> npm run build:ssr && npm run serve:ssr
> open http://localhost:4000

You have now fully universal Angular InstantSearch application running on your server and browser! If you want to run the application directly we provide a complete example that you can find on the angular-instantsearch GitHub repository.

Did you find this page helpful?