Angular Universal中Resolver用@Inject(REQUEST)致白屏的原因及替代方案
Angular Universal中注入
REQUEST导致白屏的原因及替代方案 问题描述
我有一个Angular Universal网站,编写了如下Resolver:
import { Inject, Injectable, OnDestroy } from '@angular/core'; import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Subscription, Observable, map, tap, forkJoin, catchError, of, pipe } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { Request } from 'express'; @Injectable() export class SessionResolver implements Resolve<boolean>, OnDestroy { constructor(@Inject(REQUEST) private request: Request) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { console.log(this.request.headers['accept-language']); return of(false); } }
但当我使用@Inject(REQUEST) private request: Request语句时,页面会短暂显示后变为白屏,且无任何报错信息。请问无法使用该语句的原因是什么,以及可行的替代方案有哪些?
原因分析
REQUEST令牌仅存在于服务器端:Angular Universal的REQUEST令牌是由Express引擎在**服务器端渲染(SSR)**过程中提供的注入依赖,客户端浏览器环境中没有对应的提供者。当路由Resolver在客户端执行时,无法获取REQUEST实例,导致依赖注入静默失败,最终终止应用运行,表现为白屏。- Resolver执行特性:Resolver会在路由激活前同时在服务器端和客户端执行,客户端侧的执行失败直接阻断了路由加载流程,引发白屏。
可行替代方案
方案1:区分环境动态获取REQUEST
使用Angular的平台判断工具,仅在服务器端通过Injector获取REQUEST,避免客户端注入失败:
import { Inject, Injectable, OnDestroy, Injector, PLATFORM_ID } from '@angular/core'; import { isPlatformServer, isPlatformBrowser } from '@angular/common'; import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Observable, of } from 'rxjs'; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { Request } from 'express'; @Injectable() export class SessionResolver implements Resolve<boolean>, OnDestroy { private request: Request | null = null; constructor( @Inject(PLATFORM_ID) private platformId: object, private injector: Injector ) { if (isPlatformServer(this.platformId)) { this.request = this.injector.get(REQUEST); } } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { if (this.request) { console.log(this.request.headers['accept-language']); } else { // 客户端环境下的 fallback 逻辑,比如读取localStorage或使用默认值 console.log('客户端环境无REQUEST实例'); } return of(false); } }
方案2:服务器端传递请求数据到客户端
在SSR阶段将需要的请求数据注入页面全局变量,客户端直接读取该变量:
- 修改服务器入口文件(如
server.ts):
app.get('*', (req, res) => { res.render('index', { req, providers: [ { provide: 'ACCEPT_LANG', useValue: req.headers['accept-language'] || 'en-US' } ] }); });
- 在
index.html中添加全局变量占位:
<script> window.__ACCEPT_LANG__ = '{{ ACCEPT_LANG }}'; </script>
- Resolver中读取数据:
import { Inject, Injectable, OnDestroy, PLATFORM_ID } from '@angular/core'; import { isPlatformServer, isPlatformBrowser } from '@angular/common'; import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Observable, of } from 'rxjs'; @Injectable() export class SessionResolver implements Resolve<boolean>, OnDestroy { private acceptLang: string; constructor( @Inject(PLATFORM_ID) private platformId: object, @Inject('ACCEPT_LANG') private serverLang: string ) { this.acceptLang = isPlatformServer(this.platformId) ? this.serverLang : (window as any).__ACCEPT_LANG__; } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { console.log(this.acceptLang); return of(false); } }
方案3:使用TransferState传递数据
利用Angular官方的TransferState API,将服务器端的请求数据传递到客户端,避免重复获取:
import { Inject, Injectable, OnDestroy, PLATFORM_ID } from '@angular/core'; import { isPlatformServer, isPlatformBrowser, TransferState, makeStateKey } from '@angular/common'; import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Observable, of } from 'rxjs'; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { Request } from 'express'; const ACCEPT_LANG_KEY = makeStateKey<string>('accept-language'); @Injectable() export class SessionResolver implements Resolve<boolean>, OnDestroy { constructor( @Inject(PLATFORM_ID) private platformId: object, private transferState: TransferState, private injector: Injector ) {} resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { let acceptLang: string | null = null; if (isPlatformServer(this.platformId)) { const request = this.injector.get(REQUEST) as Request; acceptLang = request.headers['accept-language'] || null; this.transferState.set(ACCEPT_LANG_KEY, acceptLang); } else { acceptLang = this.transferState.get(ACCEPT_LANG_KEY, null); } console.log(acceptLang); return of(false); } }
内容的提问来源于stack exchange,提问作者Andrew Howard
相关产品推荐
相关产品推荐

