3

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

sushmit sarmah
  • 7,508
  • 6
  • 21
  • 24
  • this is because you are using the normal angular way for your http call and ngrx for updating variables ,try and use ngrx effects so that the state will be in sink and there wont be multiple places wghere data is maintained – Rahul Singh Aug 09 '17 at 05:15

3 Answers3

3

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.

Stephen R. Smith
  • 3,310
  • 1
  • 25
  • 41
  • That was helpful thanks. I was wondering what was slowing things down as tie progressed. But the problem of the question persists. The first variable change is not detected. – sushmit sarmah Aug 09 '17 at 08:01
  • The unsubscribe can be done slightly simpler with .takeWhile(_ => this.iAmAlive) where iAmAlive is a simple boolean initialized to true. onDestory then just sets iAmAlive to false, and the subscription ends. – Richard Matsen Aug 10 '17 at 10:34
2

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);
        });
    });
}
sushmit sarmah
  • 7,508
  • 6
  • 21
  • 24
0

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.

Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • The code I have given is a sample. On subscribe I receive the data to manipulate for several d3 charts, and also to display in different places in different formats. I have used it where I just need to display certain data. – sushmit sarmah Aug 10 '17 at 09:55
  • 1
    Ok, understood. I have a d3 graph in my Angular project, wrapped in a component of course. I actually turn off change detection (with detach method), and attach the svg to a div in the template with d3.select('.d3-graph').append('svg')... The graph renders fine, but I'm not expecting the data to change and therefore no dynamic update of the graph. Are you needing dynamic update, i.e an observable pipeline? – Richard Matsen Aug 10 '17 at 10:26
  • yea I am updating on click of an icon in googlemap which is a directive in one component. The d3 chart is in a component like a sidebar. Its just easier to update things using stores. and since I have multiple charts which require data in different formats i have an injectable helper class to manipulate the data on subscribe update. All the d3 charts update on change of data. – sushmit sarmah Aug 10 '17 at 10:42
  • In that case, I think you had it right the first time with chRef.detectChanges(). Any reason why ngZone.run() might be better? – Richard Matsen Aug 10 '17 at 10:47
  • yea i know chRef.detectChanges() was more local but it was throwing errors on some cases. I have an if condition inside the subscribe and I had to put chRef.detectChanges() inside each condition like if(){...; chRef.detectChanges() } else { ...; chRef.detectChanges() }. if I didn't it was throwing errors. And even in this case there were times it was throwing errors ( not all the time but once in a while). And I currently don't have time to debug why. ngzone seems to work fine without any performance detriment so good enough for me now.. maybe in the future I will try to find out why – sushmit sarmah Aug 10 '17 at 11:54