Angular如何在DOM加载前执行服务函数 解决品牌数据加载不同步问题
解决方案
Angular 有两种成熟的方案可以实现品牌数据加载完成前阻止用户交互和页面渲染,推荐优先使用第一种官方原生方案。
方案1:使用 Angular 内置的 APP_INITIALIZER 令牌(最优方案)
APP_INITIALIZER 是 Angular 提供的应用初始化依赖令牌,你可以用它注册初始化任务,Angular 会在启动应用、渲染任何组件之前先执行完所有注册的初始化任务,完全符合你的需求。
第一步:修改 brandService 的 getBrand 方法
原来的方法直接在内部 subscribe 了,需要改成返回 Observable 供 APP_INITIALIZER 订阅:
// Get a Brand into cache,改成返回 Observable getBrand(url: string): Observable<BrandLookup> { this.brandRequest = { Url: url, BrandLookupType: 'review' }; return this.http.post<BrandLookup>(this.api + this.controller + 'getbrandlookup', this.brandRequest) .pipe( tap(res => { console.log(new Date().toString(), this.constructor.name, '=> getBrand()', res); this.brandLookup = res; const bronzeMin = this.brandLookup.pricingMatrix.singleWillPrice; const silverMin = this.brandLookup.pricingMatrix.singleWillPrice + this.brandLookup.pricingMatrix.lifetimeInterestPrice; const goldMin = this.brandLookup.pricingMatrix.singleDWTPrice; localStorage.setItem('brandCode', this.brandLookup.brandCode); localStorage.setItem('ssoUrl', this.brandLookup.ssoUrl); localStorage.setItem('cpUrl', this.brandLookup.customerPortalUrl); localStorage.setItem('reviewUrl', this.brandLookup.reviewUrl); localStorage.setItem('brand', JSON.stringify(this.brandLookup.brand)); // Set Bronze Landing Page price if (bronzeMin === null) { localStorage.setItem('bronzeMin', '89'); } else { localStorage.setItem('bronzeMin', bronzeMin.toString()); } // Set Silver Landing Page Price if (silverMin === null) { localStorage.setItem('silverMin', '239'); } else { localStorage.setItem('silverMin', silverMin.toString()); } // Set Gold Landing Page Price if (goldMin === null) { localStorage.setItem('goldMin', '550'); } else { localStorage.setItem('goldMin', goldMin.toString()); } this.broadcastBrandUpdate(); }) ); }
第二步:在 AppModule 中注册 APP_INITIALIZER
打开你的 app.module.ts,添加如下配置:
import { NgModule, APP_INITIALIZER } from '@angular/core'; import { BrandService } from './你的brandService路径'; // 初始化工厂函数 export function brandInitializer(brandService: BrandService) { return () => brandService.getBrand(window.location.origin).toPromise(); } @NgModule({ // ... 其他原有配置 providers: [ // ... 其他原有服务 { provide: APP_INITIALIZER, useFactory: brandInitializer, deps: [BrandService], multi: true } ] }) export class AppModule { }
第三步:删除 app.component.ts 中原来的 getBrand 调用
因为现在品牌数据已经在应用启动前就加载完成了,你原来在 ngOnInit 里的调用可以直接删掉,避免重复请求。
补充说明:如果要处理品牌请求失败的场景,可以在getBrand的pipe里添加catchError逻辑,跳转到错误页或者重试,避免应用卡住;如果担心API请求超时导致白屏过久,可以给http请求加timeout操作符,设置超时时间,超时后走降级逻辑比如用默认品牌数据,或者提示用户刷新。
方案2:根组件加全局加载遮罩(适合需要自定义加载态的场景)
如果你需要在加载品牌数据的时候给用户显示加载动画,可以用这个方案:
第一步:给 BrandService 添加加载状态
在 brandService 里加一个可观察的加载状态:
public brandLoaded$ = new BehaviorSubject<boolean>(false); // 原来的getBrand的subscribe逻辑最后加一行: this.brandLoaded$.next(true);
第二步:修改 app.component 的模板和逻辑
app.component.ts:
brandLoaded = false; ngOnInit() { this.brandService.brandLoaded$.subscribe(loaded => { this.brandLoaded = loaded; }); this.brandService.getBrand(window.location.origin); }
app.component.html:
<!-- 加载遮罩,品牌加载完成前显示 --> <div *ngIf="!brandLoaded" class="loading-overlay"> <!-- 这里放你的加载动画/提示 --> <p>加载中,请稍候...</p> </div> <!-- 品牌加载完成后才显示路由内容 --> <router-outlet *ngIf="brandLoaded"></router-outlet>
给加载遮罩加样式固定铺满全屏,阻止用户点击底层内容:
.loading-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #fff; display: flex; align-items: center; justify-content: center; z-index: 99999; }
内容的提问来源于stack exchange,提问作者Web Develop Wolf
相关产品推荐
相关产品推荐

