I define an observable (result$) in a component and I display it on its template via the async pipe. The observable is a combination of other 2 observables (first$, second$) via combineLatest. If one of the observables, or both, emits too early (before ngAfterContentInit I have found), the resulting observable won't emit a value.
Component: does not work
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
result$: Observable<number>;
first$ = new Subject<number>();
second$ = new Subject<number>();
constructor() {}
ngOnInit(){
this.result$ = combineLatest(
this.first$,
this.second$
).pipe(
map(([first, second]) => {
// This is not printed to the console
console.log('combined obs emitted value');
return first + second;
})
);
console.log('first and second emit value');
this.first$.next(2);
this.second$.next(4);
}
ngAfterContentInit() {
console.log('ngAfterContentInit');
}
}
The order of execution is:
1.first and second emit value
2.ngAfterContentInit
My assumption here is that in ngAfterViewInit the template has been rendered and the subscriptions have been made. Because the observables emit a value before this, the component is not notified. This can only mean the resulting observable is cold (therefore, you need to subscribe before it emits a value). The 2 observables are Subjects, so my assumption is that subjects are cold observables. Is this correct?
If I delay the emission of first$ and second$, everything works: Component: first$ and second$ emit later @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: [ './app.component.css' ] }) export class AppComponent {
result$: Observable<number>;
first$ = new Subject<number>();
second$ = new Subject<number>();
constructor() {}
ngOnInit(){
this.result$ = combineLatest(
this.first$,
this.second$
).pipe(
map(([first, second]) => {
console.log('combined obs emitted value');
return first + second;
})
);
// Solution 1: add timeout
setTimeout(() => {
console.log('first and second emit value');
this.first$.next(2);
this.second$.next(4);
})
}
ngAfterContentInit() {
console.log('ngAfterContentInit');
}
}
The order is now:
ngAfterContentInit
first and second emit value
combined obs emitted value
So again, is this because the subscription is made before the observables emit a value?
If I change the observables to BehaviorSubject, everything works too even if the values are emitted before the subscription takes place. Does this mean BehaviourSubjects are hot observables? Component: works
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
result$: Observable<number>;
first$ = new BehaviorSubject<number>(0);
second$ = new BehaviorSubject<number>(0);
constructor() {
}
ngOnInit(){
this.result$ = combineLatest(
this.first$,
this.second$
).pipe(
map(([first, second]) => {
// This is not printed to the console
console.log('combined obs emitted value');
return first + second;
})
);
console.log('first and second emit value');
this.first$.next(2);
this.second$.next(4);
}
ngAfterContentInit() {
console.log('ngAfterContentInit');
}
}
Q1: The 2 observables are Subjects, so my assumption is that subjects are cold observables. Is this correct?
A1: The answer from this question says that the subject itself is hot. This article describes hot, cold and subjects.
Q2: So again, is this because the subscription is made before the observables emit a value?
A2: Yes. Subscription to Subject will receive values AFTER you subscribe. The delay() you introduced should have given time for this to happen.
Why? This is because a BehaviorSubject
always hold a value and emits it when subscribed, while a Subject
does not hold a value, it just emits values from producer to current subscribers. Details.
Q3: Does this mean BehaviourSubjects are hot observables?
A: See A1.
Apologies if this does not answer your question directly. I am just trying to say that...when dealing with Subject and BehaviorSubject, I do not really care if they are hot or cold.
Instead, I ask: "Do I want the observable to always hold a value when I subscribe?". If yes, I use a BehaviorSubject
. If I am ok with no value upon subscription, then Subject
is ok. Other than this difference, both of them will receive emitted values after subscription. --- depends on your use case.