Angular中NgOnInit与constructor执行顺序及异步调用先后控制问题
Angular生命周期与异步流顺序问题解答
1. constructor和ngOnInit的执行顺序
Angular 组件类的constructor 执行顺序完全早于 ngOnInit,二者是串行执行,不存在并行的情况。
你遇到的输出顺序问题和生命周期无关,本质是异步 Observable 回调的执行时机问题:constructor 里的 firestore 请求是异步操作,subscribe 只是注册回调函数,不会阻塞主线程执行,所以 constructor 注册完回调后就会继续执行后续逻辑,直到触发 ngOnInit 注册路由参数的订阅。此时如果路由参数先就绪,回调就会先触发打印 undefined,等 firestore 异步请求返回后才会打印getting settings。
2. 为什么forkJoin没有输出
forkJoin 触发订阅回调的前提是所有传入的 Observable 都至少 emit 一次值,且全部主动 complete,你用到的两个流都不满足这个条件:
valueChanges()是 Firestore 提供的长监听流,只要对应文档更新就会持续推送新值,不会主动 completequeryParams是 Angular 路由提供的长监听流,只要路由参数变更就会推送新值,也不会主动 complete
所以两个流都不会触发 complete 事件,forkJoin 永远等不到执行回调的时机,自然没有任何输出。
3. 适配需求的解决方案
你的需求是:仅从 Firestore 获取一次 settings,拿到 settings 后再处理路由参数,路由参数允许为空。推荐使用take(1)+switchMap的组合方案,代码如下:
import { take, switchMap } from 'rxjs/operators'; // 最佳实践:constructor仅做依赖注入,不要写业务逻辑 constructor(private firestore: AngularFirestore, private activatedRoute: ActivatedRoute) {} ngOnInit() { this.firestore.collection('settings').doc('settings').valueChanges() .pipe( take(1), // 只取一次返回值,之后自动结束当前流 switchMap(settings => { // 拿到settings后先赋值 this.settings = settings; console.log('getting settings'); // 切换到路由参数流,加take(1)表示只取当前一次参数,不加则后续参数变更都会触发回调 return this.activatedRoute.queryParams.pipe(take(1)); }) ) .subscribe(params => { console.log(this.settings); // 此处settings必然已经赋值 this.queryParams = params; if (this.queryParams?.['myParam']) { this.setmyParamTrue(); } }); }
如果后续有多处逻辑都需要依赖 settings 就绪后执行,可以用 BehaviorSubject 做状态中转,解耦 settings 获取和后续业务逻辑:
import { BehaviorSubject } from 'rxjs'; import { take, filter, switchMap } from 'rxjs/operators'; // 定义settings就绪状态的Subject,初始值为false private settingsReady$ = new BehaviorSubject<boolean>(false); settings: any; constructor(private firestore: AngularFirestore, private activatedRoute: ActivatedRoute) { // 单独处理settings获取逻辑 this.firestore.collection('settings').doc('settings').valueChanges() .pipe(take(1)) .subscribe(settings => { this.settings = settings; console.log('getting settings'); this.settingsReady$.next(true); }); } ngOnInit() { // 等待settings就绪后再处理路由参数 this.settingsReady$ .pipe( filter(ready => ready), // 过滤掉未就绪的状态 take(1), switchMap(() => this.activatedRoute.queryParams.pipe(take(1))) ) .subscribe(params => { console.log(this.settings); // 路由参数处理逻辑 }); }
内容的提问来源于stack exchange,提问作者shredder
相关产品推荐
相关产品推荐

