Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Server side rendering doesn't wait for API calls to finish

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.

like image 589
Aniket Avatar asked Nov 08 '17 05:11

Aniket


1 Answers

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;
  });
}
like image 182
Frank Goortani Avatar answered Nov 03 '22 02:11

Frank Goortani