Jihoon Kwon
Jihoon Kwon

Reputation: 755

Angular5 Server side rendering, external Api data service does not work in ssr

I have set up and used successfully Angular5 + SSR. It is still pretty nice.

All components work well on SSR and Non-SSR. And there are some services which call external HTTP get APIs to get some data. Of course, it works well on a Non-SSR mode.

But, the problem is that on SSR, the node server does not support to fetch and render the data. I can only see the fetched data after client-side fetching and rendering.

import {Injectable} from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import 'rxjs/add/operator/map';
import {Observable} from "rxjs";

const httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable()
export class BannerService {

    constructor(public http: HttpClient){
        console.log('SmsService Initialized...');
    }

    getBanners(){
        return this.http.get(BASE_API_URL + '/getData', httpOptions);
    }
}

home.component.ts

import {Component, OnDestroy, OnInit} from '@angular/core';
import { Router } from "@angular/router";
import {Subscription} from "rxjs/Subscription";
import {BannerService} from "../../services/banner.service";

@Component({
    selector: 'app-home',
    styleUrls: ['home.container.css'],
    templateUrl: 'home.container.html'
})

export class HomeContainerComponent implements OnInit, OnDestroy {

    public horizontalBannerList1;
    public horizontalBannerList2;
    public verticalBannerList;
    private bannerList;

    constructor( private router: Router, private bannerService: BannerService){
         ...
    }

    ngOnInit() {
        this.initBannerList();
    }

    ngOnDestroy() {
       ...
    }

    initBannerList(){

        if(this.bannerList){
            return;
        }

        this.bannerService.getBanners().subscribe(
            result => {
                console.log("bannerList result : ", result);
                this.bannerList = result;    
            },
            error => {
                console.error("bannerList error: ", error);
            },
            () => {
                console.log("bannerList completed");
            });
    }

}

I expected that on SSR the node server calls HTTP request data and render it on index.html but it's not...

Am I missing or misunderstood?

ps : The same issues are reported. https://github.com/angular/universal/issues/674 If I solve these issues or find out the good doc, I would update it again. :)

Upvotes: 18

Views: 7432

Answers (2)

Ali.ro
Ali.ro

Reputation: 54

Angular Universal’s TransferState allows you to share the fetched data between the server and client.

you can use it like this

banner.service.ts:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { makeStateKey, TransferState } from '@angular/platform-browser';
import { isPlatformServer } from '@angular/common';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
};
const BANNERS_KEY = makeStateKey<any>('banners');

@Injectable()
export class BannerService {
  constructor(
    private http: HttpClient,
    private transferState: TransferState,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {}

  getBanners(): Observable<any> {
    if (this.transferState.hasKey(BANNERS_KEY)) {
      return new Observable((observer) => {
        // Use cached data if it exists
        observer.next(this.transferState.get(BANNERS_KEY, null));
        this.transferState.remove(BANNERS_KEY); // Clean up after SSR
        observer.complete();
      });
    } else if (isPlatformServer(this.platformId)) {
      // Server-side data fetching
      return this.http.get(BASE_API_URL + '/getData', httpOptions).pipe(
        tap((data) => {
          this.transferState.set(BANNERS_KEY, data);
        })
      );
    } else {
      // Client-side data fetching
      return this.http.get(BASE_API_URL + '/getData', httpOptions);
    }
  }
}

and home.component.ts will be like:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { BannerService } from '../../services/banner.service';

@Component({
  selector: 'app-home',
  styleUrls: ['home.container.css'],
  templateUrl: 'home.container.html',
})
export class HomeContainerComponent implements OnInit, OnDestroy {
  public horizontalBannerList1: any;
  public horizontalBannerList2: any;
  public verticalBannerList: any;
  private bannerList: any;

  constructor(private bannerService: BannerService) {}

  ngOnInit() {
    this.initBannerList();
  }

  ngOnDestroy() {}

  initBannerList() {
    if (this.bannerList) {
      return;
    }

    this.bannerService.getBanners().subscribe(
      (result) => {
        console.log('bannerList result : ', result);
        this.bannerList = result;
        // Further process the banners as required
      },
      (error) => {
        console.error('bannerList error: ', error);
      },
      () => {
        console.log('bannerList completed');
      }
    );
  }
}

Upvotes: 1

AJ Developer
AJ Developer

Reputation: 11

You need to check using Angular Universal features like resolvers or the TransferState API to pre-fetch data You can also optimize API response to SSR, Conditional rendering can also be used to display loading indicators during SSR while fetching data asynchronously on the client side.

Upvotes: 0

Related Questions