給 Angular 伺服器端渲染應用設定一個渲染超時時間

注销發表於2022-05-22

我們用 setTimeout 模擬一個需要 5 秒鐘才能完成呼叫的 API:

const express = require('express');

const app = express();

app.get('/api/fast', (req, res) => {
  console.log('fast endpoint hit');
  res.send({response: 'fast'});
});

app.get('/api/slow', (req, res) => {
  setTimeout(() => {
      console.log('slow endpoint hit');
      res.send({response: 'slow'});
  }, 5000);
});

app.listen(8081, () => {
  console.log('Listening');
});

然後新建一個 Angular service,呼叫這個 /api/slow:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
 providedIn: 'root'
})
export class CustomService {

 constructor(private http: HttpClient) {}

 public getFast(): Observable<any> {
   return this.http.get<any>('http://localhost:8081/api/fast');
 }

 public getSlow(): Observable<any> {
   return this.http.get<any>('http://localhost:8081/api/slow');
 }
}

在伺服器端渲染模式下,等待這個 API 呼叫的返回,至少需要花費 5 秒鐘。我們可以給這個 API 呼叫設定一個超時機制。如果伺服器端渲染時超過我們指定的超時間隔,還沒有得到 API 響應,我們就放棄這次 API 呼叫,讓其在客戶端渲染模式下繼續進行。

我們使用 RouteResolver 來實現。

從 Angular route 框架匯入 router

import { Resolve } from '@angular/router';

從 Angular common 開發包匯入 Angular 執行環境監測的 API:

import { isPlatformBrowser } from '@angular/common';

匯入 injection token,獲得當前執行的 platform id:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';

新建一個 service class:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { Resolve } from '@angular/router';
import { Observable, timer } from 'rxjs';
import { isPlatformBrowser } from '@angular/common';
import { CustomService } from './custom.service';
import { takeUntil } from 'rxjs/operators';

@Injectable({
 providedIn: 'root'
})
export class SlowComponentResolverService implements Resolve<any> {

 constructor(private service: CustomService, @Inject(PLATFORM_ID) private platformId: any) { }

 public resolve(): Observable<any> {
   if (isPlatformBrowser(this.platformId)) {
     return this.service.getSlow();
   }

如果當前應用執行於瀏覽器端,上圖的 isPlatformBrowser(this.platformId) 返回 true,因此直接呼叫慢速 API.

否則建立一個 Observable,500 毫秒後發射值:

const watchdog: Observable<number> = timer(500);

我們將這個 watchDog Observable 透過 pipe 設定到 this.service.getSlow 的下游。這樣,如果 this.service.getSlow() 返回的 Observable 在 500 毫秒之內不 emit 值的話,watchdog 就會向 Component push null 值,否則,API 的真實 response 會推送給 Component.

我們需要更新應用相關的 routing 程式碼來消費這個 Resolver.

給 slowComponent 分配一個 resolver:

const routes: Routes = [
{path: '', redirectTo: 'fast', pathMatch: 'full'},
{path: 'fast', component: FastComponent},
{path: 'slow', component: SlowComponent, resolve: {response: SlowComponentResolverService}}
];

在 slowComponent 的實現程式碼裡,從分配的 Route resolver 裡讀取 API response 資料:

import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
selector: 'app-slow',
template: `
  <p>
    Response is: {{response | json}}
  </p>
`,
styles: []
})
export class SlowComponent {

public response: any = this.router.snapshot.data.response;
constructor(private router: ActivatedRoute) {}
}

注意這裡並沒有直接訪問 Route Resolver:this.router.snapshot.data.response

當 API 在 500 毫秒之內返回時,所有的 slowComponent 原始碼都由伺服器端生成:

當 API 500 毫秒未能成功返回資料,則客戶端會再次呼叫該 API,然後在客戶端完成渲染:

相關文章