10
votes

Hi I am using Angular version 4.3 and I have implemented server side rendering as mentioned in this article.

https://medium.com/@cyrilletuzi/angular-server-side-rendering-in-node-with-express-universal-engine-dce21933ddce

When I hit the server url to get the server side rendered HTML, the component's initial data do come up which are static and doesn't depend on any API calls.

But the problem is that the rendering does not contain data that should be present after completing the AJAX request.

I have written the call for API in my component's ngOnInit method. It calls a service which invokes angular's http service's get method to fetch the data. The angular universal rendering does call this function. The call is made but does not wait for the response to come back. It instead returns the initial data. And after some time in the server console, I do see that the response has come back to the server. But by that time angular universal has already returned.

Please help on how to solve this issue.

1
How does your API call look like? Maybe you could use the HttpClient Module built in Angular for your purpose. It works great with SSR: angular.io/guide/http Is it necessary that the API call is inside the ngOnInit()method? To be sure the component is able to load and the data is present you could use a resolver instead, this way you can be sure the data is available in the component: angular.io/api/router/Resolve - Denis
i use async ngOnInit() { this.message = 'Hello!!'; var data = await this.http.get('https://api.github.com/users/github').toPromise(); this.message = data['bio']; } and it doesn't wait. It shows initial message and then make XHR call. What was the point of Angular Universal then - Toolkit

1 Answers

2
votes

I understand this question has been around for a while. This is how we found a workaround in LastCall.cc using a resolver (not very performant though)

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import {
  Resolve,
  RouterStateSnapshot,
  ActivatedRouteSnapshot
} from '@angular/router';
import { makeStateKey, TransferState } from '@angular/platform-browser';
import { SearchService } from '@app/services/search.service';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';

@Injectable()
export class ProductDetailsResolver implements Resolve<any> {
  private result: any;
  constructor(
    private readonly state: TransferState,
    private search: SearchService
  ) { }

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> {
    const id = route.paramMap.get('dealId');
    const RESULT_KEY = makeStateKey<any>('product-details-resolver.result' + id);
    if (this.state.hasKey(RESULT_KEY)) {
      const res = of(this.state.get<any>(RESULT_KEY, null));
      this.state.remove(RESULT_KEY);
      return res;
    } else {
      this.state.onSerialize(RESULT_KEY, () => this.result);
      return this.search.searchDealById(id)
        .do(result => {
          this.result = result;
        }).catch((error) => {
          console.log('API Error --> ', error);
          this.result = {};
          return Observable.of({});
        });
    }
  }
}

The service above can be any async data fetcher as long as it returns an Observable

The StateTransfer handshake will force the resolver to wait for API results. The catch is, there is no timeout and if your API has any issues, it will seem as if it's hanging for the end-user.

Here is how you implement the route with resolver:

path: 'campaigns',
children: [
  {
    path: ':dealId',
    component: EcommerceProductDetails,
    resolve: { result: ProductDetailsResolver },
    runGuardsAndResolvers: 'paramsOrQueryParamsChange'
  }
]

and here is how to get the data in the component:

ngOnInit() {
  this.route.data.subscribe(data => {
    this.deal = <IDeal>data.result._source;
  });
}