NgOnInit() 在构造函数之后/并行运行

Posted

技术标签:

【中文标题】NgOnInit() 在构造函数之后/并行运行【英文标题】:NgOnInit() Runs after/in parallel with constructor 【发布时间】:2021-11-27 09:32:23 【问题描述】:
  constructor(private firestore: AngularFirestore, private activatedRoute: ActivatedRoute) 
 this.firestore.collection('settings').doc('settings').valueChanges().subscribe(settings => 
  console.log('getting settings');
  this.settings = settings;
);

ngOnInit() 
this.activatedRoute.queryParams.subscribe(params => 
   console.log(this.settings);
  

然而,我的控制台输出是

未定义

获取设置

我怎样才能让一个接一个地跑?没有把所有东西都放在 NgOnInit 中,整个代码在 .then()

之后

编辑: 我已将代码编辑为以下建议的答案

使用 forkJoin:

  ngOnInit() 
forkJoin([this.firestore.collection('settings').doc('settings').valueChanges(),
  this.activatedRoute.queryParams]).subscribe(values => 
  this.settings = values[0];
  console.log('I am a console');
  console.log(this.settings);
  this.queryParams = values[1];
  if (this.queryParams['myParam']) 
    this.setmyParamTrue();
  
);

但是,无论是否有任何 queryParams,都不会进入控制台

不用说,我只需要从 firestore 中检索设置一次,而且参数不必存在。

【问题讨论】:

构造函数总是首先触发(所以在 ngOnInit 之前) - 但是,您在构造函数中执行异步操作,并且您的控制台位于回调内部,因此稍后触发。一个提示——不要在构造函数中做实际的工作——在 ngOnInit 中做。要解决此问题,您需要使用 RxJs 运算符 - 就像在这种情况下,您可以 switchMap 或使用 combineLatest 一次订阅两个可观察对象。 我已经编辑了我的帖子。它实际上是在构造函数中来说明我的观点 【参考方案1】:

情况

来自您的原始代码:

1  class SomeComponent 
2  
3    constructor(private firestore: AngularFirestore, private route: ActivatedRoute) 
4      this.firestore.collection().doc().valueChanges().subscribe(settings => 
5        console.log('getting settings');
6        this.settings = settings;
7      );
8    
9  
10   ngOnInit() 
11     this.route.queryParams.subscribe(params => 
12       console.log(this.settings);
13     
14   
15 

在第 4 行的 constructor 中,您只是创建了一个 Subscription。您传递给.subscribe() 方法的逻辑将在每次可观察对象发出一个值时执行。

在第 11 行的 ngOnInit 中,您正在创建另一个订阅。

回调函数的执行顺序完全取决于每个 observable 的发射时间,而不是您注册订阅的时间! (显然在您的情况下,来自ActivatedRoute 的可观察对象首先发出,然后是来自AngularFireStore 的观察对象。

NgOnInit() 在构造函数之后/并行运行

希望你能明白为什么这个说法不正确!


目标

我怎样才能一个接一个地跑?

您正试图在您的回调中访问this.settings,但它还没有准备好!解决方案是创建一个仅在 settings 值准备好后才开始的 observable。

让我们将您的设置定义为可观察对象:

1  class SomeComponent 
2
3    private settings$ = this.firestore.collection().doc().valueChanges();
4    
5    constructor(private firestore: AngularFirestore, private route: ActivatedRoute)  
6  
7    ngOnInit() 
8      this.settings$.pipe(
9        switchMap(settings => this.route.queryParams.pipe(
10         tap(params => console.log('both values',  settings, params ))
11       ))
12     ).subscribe();
13   
14 

您在第 8 行看到,我们从this.setting$pipe 开始,将其排放到switchMap,这将订阅内部源并排放源的排放。在这种情况下,您的内部来源是可观察的参数。

我们将pipe 放在可观察的内部参数上,以便tap 可以访问settings 和发出的params

tap 对执行副作用很有用,通常比将逻辑放入订阅回调中更受欢迎。


根据您在ngOnInit 中订阅此订阅的目的,单独定义可观察对象可能会有所帮助,就像我们对settings$ 所做的那样:

1  class SomeComponent 
2
3    private settings$ = this.firestore.collection().doc().valueChanges();
4    private doWork$ = this.settings$.pipe(
5      switchMap(settings => this.route.queryParams.pipe(
6        tap(params => console.log('both values',  settings, params ))
7      ))
8    );
9
10   constructor(private firestore: AngularFirestore, private route: ActivatedRoute)  
11 
12   ngOnInit() 
13     this.doWork$.subscribe();
14   
15 

【讨论】:

感谢您的详尽回复。但是,您的第二个代码块出现了很多错误:来自 tslint:TS2345:“Observable”类型的参数不可分配给“OperatorFunction”类型的参数。类型 'Observable' 与签名 '(source: Observable): Observable' 不匹配。 TS2684:'void' 类型的 'this' 上下文不可分配给 'Observable' 类型的方法的 'this' ......因为我不能真正把它放在评论中,我上传了我 ide 的图像:i.imgur.com/wsiwWDO.png 没关系,必须从 rxjs/operators 导入 switchmap,而不是 rxjs-compat/operator/switchmap。但是,phpstorm 说它已弃用 另一件事是,queryParams 实际上不应该依赖于设置。是的,一个查询参数使用设置,但由于代码的其他地方也使用了设置,我认为首先获取设置然后解析查询对于可读性和结构更有意义 我不知道为什么会说 switchMap 已弃用,它不是:-) 使用此代码,queryParams 不依赖于settings$doWork$ 依赖于先获取设置,然后使用queryParams。如果在代码的其他地方使用了设置,您也可以简单地声明其他可观察对象也以settings$ 开头。 (doWork2$ = settings$.pipe(...))【参考方案2】:

有两个运算符可以帮助您将 2 个 observables 相互使用:

    forkJoin 结合最新 切换地图

如果你需要同时运行它,请使用forkJoin,如下所示:

constructor(
  private firestore: AngularFirestore, 
  private activatedRoute: ActivatedRoute) 
 
  forkJoin([
   this.firestore.collection('settings').doc('settings').valueChanges(),
   this.activatedRoute.queryParams
   ]).subscribe(values => 
      this.settings = values[0]; //<- you'll get settings here
      console.log(values[1]);
      // use the call accordingly.
   )


同样,如果您需要连续收听,可以使用combineLatest。并使用switchMap。完全取决于用例。

【讨论】:

我已经编辑了我的帖子

以上是关于NgOnInit() 在构造函数之后/并行运行的主要内容,如果未能解决你的问题,请参考以下文章

如何运行构造函数或 ngOnInit 两次或更多次

Angular8路由:强制所有子代运行其构造函数ngOnInit

在构造函数或 ngOnInit 中加载异步函数

构造函数和 ngOnInit 的区别

构造函数和 ngOnInit 的区别

构造函数和 ngOnInit 的区别