Home > Back-end >  NgOnInit() Runs after/in parallel with constructor
NgOnInit() Runs after/in parallel with constructor

Time:10-07

  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);
  }
}

my console output, however, is

undefined

getting settings

How can I make one run after the other? Without putting everything in NgOnInit with whole code going after .then()

Edit: I have edited the code as an answer below suggested

With 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();
  }
});

However, nothings goes to console, with or without any queryParams

Needless to say, I need to retrieve settings from firestore only once and paramters don't have to exist.

CodePudding user response:

There are two operators which might help you to use 2 observables with each other:

  1. forkJoin
  2. combineLatest
  3. switchMap

In case you need to run it once you have both, use forkJoin as below:

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.
   })
}

Similarly, you can use combineLatest if you need to listen continuously. and also use switchMap. Depends on the use case totally.

CodePudding user response:

The Situation

From your original code:

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 }

In the constructor on line #4, you are simply creating a Subscription. The logic you pass to the .subscribe() method will be executed each time the observable emits a value.

In ngOnInit on line #11, you are creating another subscription.

The order in which the callback functions are executed is completely dependent on when each observable emits, NOT when you registered the subscription! (Obviously in your case the observable from ActivatedRoute is emitting first, then later the one from AngularFireStore.)

NgOnInit() Runs after/in parallel with constructor

Hopefully you can see why this statement is not true!


The Goal

How can I make one run after the other?

You are trying to access this.settings in your callback, but it isn't ready yet! The solution is to create an observable that begins only once the settings value is ready.

Let's define your settings as an 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 }

You see on line #8 we start with this.setting$ and pipe its emissions to switchMap which will subscribe to an inner source and emit the source's emissions. In this case, your inner source is the params observable.

We put a pipe on the inner params observable so that tap would have access to both settings and the emitted params.

tap is useful to execute side effects and is often preferred to putting logic in subscribe callback.


Depending on what your purpose is for this subscription inside ngOnInit, it can be helpful to also define the observable separately, as we did with 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 }
  • Related