Reputation: 1996
I have a custom BehaviourSubject in my Angular app, that is used as an Observable in my template.
When I call next on the Subject I expect the Subject in the template to be resolved. I use the async
pipe to listen to asynchronous changes in this Subject.
I created an example in Stackblitz to show the problem that I have: https://stackblitz.com/edit/angular-kmou5e
In app.component.ts
I created an example asynchronous method, that sets a value to this.data
after a timeout. As soon as this.data
is set, hello.component.ts
is initialized and rendered. I listen to OnChanges
in HelloComponent
and call next()
on the custom Subject within HelloComponent
.
As you can see in the template of HelloComponent
I expect the observed Subject customSubject$
to be rendered as an <h1>
as soon as is gets a value through next()
.
You can see, that the subscription of the customSubject$
is called, as shown in the logs. Only the template doesn't render this Observable/Subject.
How can I get the template to render when using a custom Subject
? Do I need to use a different pipe?
FYI: I also tried to assign the value of the Subject to a new Observable using combineLatest, but it didn't work as well.
Upvotes: 6
Views: 7758
Reputation: 722
Very good question. The problem with this is the order of lifecycle hooks Subject does not hold values, it just multi-casts to subscribers if there aren't any tough luck. Now life cycle runs in this order. Constructor, ngOnChanges, OnNgInit ..... then it loads the views and child components. So for you to see this value you need to buffer it by using a BehaviorSubject.
To see what I am talking about
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { combineLatest, Observable, Subject } from 'rxjs';
@Component({
selector: 'hello',
template: `<h1>{{ customSubject$ | async }}</h1>
<h2>{{test}}</h2>
`
})
export class HelloComponent implements OnChanges {
@Input() name: string;
public customSubject$: Subject<string>;
public test ='Before ngOnChanges run';
constructor() {
console.log('constructed');
this.customSubject$ = new Subject();
this.customSubject$.subscribe(s => {
console.log('customSubject$ subscription resolved with ' + s);
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes.hasOwnProperty('name')) {
console.log('ngOnChanges is called with ' + this.name);
this.customSubject$.next(this.name);
}
this.test ='After ngOnChanges Ran';
}
}
You have a test variable its value is changed in ngOnChanges. You will never get a glimpse of the value 'Before ngOnChanges run' because the view was not initialized yet.
Now your *ngIf='data'
is confusing you further because the component is not constructed before ngIf resolves to true
It helps to increase your delay to see this
ngOnInit() {
setTimeout(() => {
this.data = 'some string';
}, 5000);
}
Upvotes: 1
Reputation: 39836
It's a timing issue. Just change your Subject to a Behavior Subject (which caches the last emitted value) and your template will display the latest emitted value.
Here is a good resource on the various types of subjects.
Upvotes: 4