Reputation: 7728
Here is the problem I am facing.
I have a variable inside a component which assigned to a directive. I am using ngrx to dispatch and susbcribe to events. So the problem is that the variable does not update the first time. After that there is no problem.
I have a google map and icons on it and on click of any icon it makes a call to the server with the id and map bounds and then with the returned data an action is dispatched.
private getFromServer(id, bound_corners){
let params = { bounds: bound_corners }
return this.restangular.all('get-data/'+id)
.customGET("", params)
.map((d:any)=>{
return d.plain();
});
}
public onClick(icon){
let bound_corners = this.getMapBounds();
this.getFromServer(icon.id, bound_corners).subscribe((d)=>{
this.store.dispatch(new action.detail(d, id));
});
}
In the component class
let temp = store.select(fromRoot.getIconDetail);
temp.subscribe((d)=>{
this.data = d;
})
In the component this.data does not get updated the first time. If I console log(this.data) then it works but it does not get updated in the html.
If I take the dispatch action out of the getFromServer subscription like this:
public onClick(icon){
let bound_corners = this.getMapBounds();
let temp_data = {name:"test","id":0};
this.store.dispatch(new action.detail(temp_data, id));
}
then it works.
Currently I have one solution which is using ChangeDetectorRef.
constructor(private chRef: ChangeDetectorRef){
let temp = store.select(fromRoot.getIconDetail);
temp.subscribe((d)=>{
this.data = d;
this.chRef.detectChanges();
});
}
I am not sure if this is the right way but I am unable to figure out whats going on or any other solution.
Any help will be greatly appreciated. Thanks
Upvotes: 3
Views: 2271
Reputation: 23463
In a Angular with a one-way-data-flow paradigm, all data subscriptions to store data should be used directly in the html with the async pipe.
You haven't shown your html, but presume it's something like <div>{{data}}</div>
.
Instead it should be <div>{{temp | async}}</div>
.
If you are referencing properties of an observable object, the async pipe needs to be bracketed: <div>{{ (temp | async).someProp }}</div>
.
Also, depending on the store initialstate it's often useful to add the safe navigation ('?') operator to avoid errors during the pre-initialized phase: <div>{{ (temp | async)?.someProp }}</div>
. Ref: link
This should make your template reactive to the store change without needing to invoke change detection (which is what you're doing with the ChangeDetectorRef soultion and the NgZone soultion). Take a look at the ngrx example apps, e.g find-book-page.ts. See the naming convention, it's useful to suffix an observable with '$', so temp$ instead of temp.
BTW, I don't see anything wrong with explicitly invoking change detection - you sometimes need to do so when wrapping 3rd party libs.
Upvotes: 0
Reputation: 7728
This is what I finally did. Would love someone to post a better solution. Thanks to Stephen for the unsubscribe suggestion.
constructor(private ngZone: NgZone){
store.select(fromRoot.getIconDetail)
.takeUntil(ngUnsubscribe)
.subscribe((d)=>{
this.ngZone.run(() => {
this.someFunction(d);
});
});
}
Upvotes: 2
Reputation: 3400
Maybe instead of assigning the subscription to a variable, execute it directly.
constructor(){
store.select(fromRoot.getIconDetail)
.subscribe(d => {
this.data = d;
})
}
It's worth noting that when you use .subscribe, you need to unsubscribe when the component is destroyed, or you'll wind up accumulating multiple subscribes on the Observable when the component is revisited and reloaded.
To prevent this, and to prevent memory leaking, you should unsubscribe to the Observable when you destroy each component.
Add these imports to your component
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
Add this in your class - I usually do this above the constructor.
private ngUnsubscribe: Subject<any> = new Subject<any>()
Add an ngOnDestroy function
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
And then add this immediately before your .subscribe (you should use this exact syntax before every .subscribe in components with multiples).
.takeUntil(this.ngUnsubscribe)
So in your case, it would look like this.
constructor(){
store.select(fromRoot.getIconDetail)
.takeUntil(this.ngUnsubscribe)
.subscribe(d => {
this.data = d;
})
}
So what happens is the subscribe will remain active until you navigate away from the component, at which point ngOnDestroy fires which unsubscribes from the Observable cleanly.
Upvotes: 3