Angular 12 SSR问题:硬重载时Header Footer先于加载器显示
Angular 12 SSR硬重载时加载器显示顺序问题解决方案
这个问题本质是SSR服务端渲染的初始HTML中已经包含了Header和Footer的内容,浏览器收到HTML后会立刻渲染这些元素,而你的加载器是靠客户端逻辑控制显示/隐藏的,所以出现了顺序颠倒的情况。下面是亲测有效的解决思路和代码:
核心思路
在服务端层面跟踪所有初始HTTP请求的状态,只有当所有请求完成后,才把Header、Footer、RouterOutlet的渲染标记传递给客户端;同时客户端同步跟踪请求状态,确保前后端状态一致。
具体实现步骤
1. 用TransferState传递加载状态
TransferState是Angular专门用来在服务端和客户端之间传递状态的工具,我们用它来标记是否所有初始请求都已完成。
2. 修改AppComponent逻辑
在app.component.ts中注入TransferState,服务端等待所有初始请求完成后更新状态,客户端读取状态控制显示:
import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core'; import { isPlatformServer } from '@angular/common'; import { TransferState, makeStateKey } from '@angular/platform-browser'; import { HttpClient } from '@angular/common/http'; // 定义状态键 const LOADING_COMPLETE_KEY = makeStateKey<boolean>('loadingComplete'); @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { loadingComplete = false; constructor( private transferState: TransferState, private http: HttpClient, @Inject(PLATFORM_ID) private platformId: Object ) {} ngOnInit(): void { // 读取服务端传递的加载状态 this.loadingComplete = this.transferState.get(LOADING_COMPLETE_KEY, false); if (isPlatformServer(this.platformId)) { // 服务端:收集所有需要等待的初始请求(替换成你项目里的实际请求) const initialRequests = [ this.http.get('/api/page-initial-data'), this.http.get('/api/header-config'), this.http.get('/api/footer-info') ]; // 等待所有请求完成后更新状态 Promise.all(initialRequests.map(req => req.toPromise())).then(() => { this.loadingComplete = true; this.transferState.set(LOADING_COMPLETE_KEY, true); }); } else { // 客户端:如果服务端未完成加载,后续通过拦截器处理请求状态 if (!this.loadingComplete) {} } } }
3. 修改AppComponent模板
根据loadingComplete状态控制元素显示,加载中只显示加载器:
<app-loader *ngIf="!loadingComplete"></app-loader> <!-- 所有请求完成后才显示页面主体 --> <div *ngIf="loadingComplete"> <app-header></app-header> <router-outlet></router-outlet> <app-footer></app-footer> </div>
4. 全局HTTP拦截器跟踪客户端请求
如果客户端还有额外的请求需要等待,添加一个拦截器来跟踪所有HTTP请求的状态:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; import { AppComponent } from './app.component'; @Injectable() export class LoadingInterceptor implements HttpInterceptor { private activeRequestCount = 0; constructor(private appComponent: AppComponent) {} intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { this.activeRequestCount++; this.appComponent.loadingComplete = false; return next.handle(request).pipe( tap({ next: (event) => { if (event instanceof HttpResponse) { this.activeRequestCount--; this.checkIfAllRequestsComplete(); } }, error: () => { this.activeRequestCount--; this.checkIfAllRequestsComplete(); } }) ); } private checkIfAllRequestsComplete(): void { if (this.activeRequestCount === 0) { this.appComponent.loadingComplete = true; } } }
然后在app.module.ts中注册拦截器:
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { LoadingInterceptor } from './loading.interceptor'; @NgModule({ // ...其他配置 providers: [ { provide: HTTP_INTERCEPTORS, useClass: LoadingInterceptor, multi: true } ] }) export class AppModule { }
额外注意事项
- 服务端要确保**所有初始数据请求(包括路由守卫的resolve、Header/Footer自身的请求)**都被加入等待列表,不然还是会出现部分元素提前渲染的情况。
- 如果使用了Angular Universal,在
server.ts中也可以全局拦截服务端的HTTP请求,统一管理加载状态。
内容的提问来源于stack exchange,提问作者Faisal Jawad
相关产品推荐
相关产品推荐

