You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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阶段将需要的请求数据注入页面全局变量,客户端直接读取该变量:

  1. 修改服务器入口文件(如server.ts):
app.get('*', (req, res) => {
  res.render('index', {
    req,
    providers: [
      { provide: 'ACCEPT_LANG', useValue: req.headers['accept-language'] || 'en-US' }
    ]
  });
});
  1. 在index.html中添加全局变量占位:
<script>
  window.__ACCEPT_LANG__ = '{{ ACCEPT_LANG }}';
</script>
  1. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 08:07:47