Angular中如何在API调用进行时提前渲染页面?服务仅执行一次
问题
需求:用户进入页面时,先渲染页面,同时发起API调用;API调用成功后仅需执行一次。
此前尝试用Resolver方案,但Resolver会等待API调用完成后才渲染页面,导致页面加载延迟(示例中延迟10秒才渲染)。尝试的代码如下:
import { Injectable } from "@angular/core"; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from "@angular/router"; import { Observable, of } from "rxjs"; import { delay, tap } from "rxjs/operators"; @Injectable({ providedIn: 'root' }) export class HashCriptoResolver implements Resolve<any> { constructor( private googleAnalyticsService: GoogleAnalyticsService ) { } resolve( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<any> { const hashService$ = of({ hash: '@$#@ERWWESDCFDSEWR@#@#' }); return hashService$ .pipe( delay(10000), tap(response => this.googleAnalyticsService.sendHash(response.hash)) ) } } // 路由配置 { path: '', resolve: { hashId: HashCriptoResolver }, }
当前问题:Resolver会等待10秒后才渲染页面,求其他可行方案?
可行方案
方案一:组件初始化时触发API调用
利用Angular组件的ngOnInit钩子,页面渲染完成后立即发起请求,同时通过单例服务标记调用状态,避免重复执行。
- 先创建控制调用次数的单例服务:
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class HashCallService { private hasCalled = false; isCalled(): boolean { return this.hasCalled; } markAsCalled(): void { this.hasCalled = true; } }
- 在目标组件的
ngOnInit中执行API调用:
import { Component, OnInit } from '@angular/core'; import { HashCallService } from './hash-call.service'; import { GoogleAnalyticsService } from './google-analytics.service'; import { of } from 'rxjs'; import { delay, tap } from 'rxjs/operators'; @Component({ selector: 'app-target', templateUrl: './target.component.html', styleUrls: ['./target.component.css'] }) export class TargetComponent implements OnInit { constructor( private hashCallService: HashCallService, private googleAnalyticsService: GoogleAnalyticsService ) {} ngOnInit(): void { if (!this.hashCallService.isCalled()) { const hashService$ = of({ hash: '@$#@ERWWESDCFDSEWR@#@#' }); hashService$ .pipe( delay(10000), tap(response => { this.googleAnalyticsService.sendHash(response.hash); this.hashCallService.markAsCalled(); }) ) .subscribe(); } } }
方案二:使用CanActivate守卫(不阻塞路由)
CanActivate守卫只需返回true就能让路由立即渲染,同时在守卫内部发起API请求,通过服务控制仅执行一次。
- 守卫代码:
import { Injectable } from "@angular/core"; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; import { Observable, of } from "rxjs"; import { delay, tap } from "rxjs/operators"; import { HashCallService } from './hash-call.service'; import { GoogleAnalyticsService } from './google-analytics.service'; @Injectable({ providedIn: 'root' }) export class HashCallGuard implements CanActivate { constructor( private hashCallService: HashCallService, private googleAnalyticsService: GoogleAnalyticsService ) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> | boolean { if (!this.hashCallService.isCalled()) { const hashService$ = of({ hash: '@$#@ERWWESDCFDSEWR@#@#' }); hashService$ .pipe( delay(10000), tap(response => { this.googleAnalyticsService.sendHash(response.hash); this.hashCallService.markAsCalled(); }) ) .subscribe(); } // 立即放行,不阻塞页面渲染 return true; } }
- 更新路由配置:
{ path: '', canActivate: [HashCallGuard], component: TargetComponent }
方案三:全局初始化(APP_INITIALIZER)
如果这个API调用是全局只需执行一次(不管进入哪个页面),可以用APP_INITIALIZER。注意它会在应用启动时执行,适合全局初始化场景,特定页面需求优先选前两个方案。
import { APP_INITIALIZER, Provider } from '@angular/core'; import { HashCallService } from './hash-call.service'; import { GoogleAnalyticsService } from './google-analytics.service'; import { of } from 'rxjs'; import { delay, tap } from 'rxjs/operators'; export function initializeHashCall( hashCallService: HashCallService, googleAnalyticsService: GoogleAnalyticsService ) { return () => { if (!hashCallService.isCalled()) { return of({ hash: '@$#@ERWWESDCFDSEWR@#@#' }) .pipe( delay(10000), tap(response => { googleAnalyticsService.sendHash(response.hash); hashCallService.markAsCalled(); }) ) .toPromise(); } return Promise.resolve(); }; } // 在模块的providers数组中添加该配置 export const HashInitializerProvider: Provider = { provide: APP_INITIALIZER, useFactory: initializeHashCall, deps: [HashCallService, GoogleAnalyticsService], multi: true };
内容的提问来源于stack exchange,提问作者carlos
相关产品推荐
相关产品推荐

