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:
- forkJoin
- combineLatest
- 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 }