Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. :)

like image 848
Jihoon Kwon Avatar asked Dec 09 '25 02:12

Jihoon Kwon


1 Answers

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');
      }
    );
  }
}
like image 82
Ali.ro Avatar answered Dec 11 '25 15:12

Ali.ro



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!