简体   繁体   中英

Getting updated values from Observable's subscription

I have noticed something about Observables in Angular 2 that I cannot explain and hope a good sould sheds light on me.

My understanding was that when subscribing to an Observable you have basically two strategies to consume the value it emits:

stream combined with async pipe, as in:

myTextSubscription$ = SomeObservable.subscribe();

{{ myTextSubscription$ | async }}

or extract the value and bind it from within the subscribe handler:

myTextSubscription$ = SomeObservable.subscribe(text => this.text = text);

{{ text }}

Well, the problem is that I have already tried a couple of times to use the latter approach and I never managed to get the text value updated, unless I invoke this.cdRef.markForCheck() within the handler. I guess invoking the changeDetection manually should not be required for such a basic scenario - at least I haven't seen that used in any of screencasts or tutorials.

Does this sound familiar to anyone? Is it my wrong understanding of the method, or is it perhaps a bug in Angular 2?


Edit:

The code above has been abstracted as I believed the problem could be explained on a more abstract level.

I cannot recall the first case when it bit me, but now the Observable comes from ngrx store, so it's basically something along these lines:

this.text$ = store.let((state$: Observable<State>) => {
  return state$.select(state => state.text);
});

This usually happens if SomeObservable is somehow initialized outside Angulars zone or uses some API that is not coverted by zone.js.

We would need to see how SomeObservable is exactly constructed to know for sure.

In fact, you shouldn't subscribe to the observable when using async . It's why async is that awesome. It handles both subscribe AND unsubscribe when your component is initialized/destroyed.

So instead of :
JS

myTextSubscription$ = SomeObservable.subscribe();

HTML

{{ myTextSubscription$ | async }}

You should rather have :
JS

myTextSubscription$ = SomeObservable;

HTML

{{ myTextSubscription$ | async }}

I'd like to clarify the use of $ in your variable name :
- an observable should be suffixed with '$'
- when you use subscribe, you do not get an observable

With respect of convention :
JS

let someObservable$ = ...;

HTML

{{ someObservable$ | async }}

Remember that :
- you need to unsubscribe manually from an observable when you use subscribe to it, except from the router and http)
- when you have a .subscribe , the value returned is of type Subscription (which means you can unsubscribe from it)

So here's an overview of a cleaner structure (without using async pipe) :

@Component({
  selector: '...',
  templateUrl: '...html',
  styleUrls: ['...css']
})
export class AppComponent implements OnInit, OnDestroy {
    private myText: string;
    private myTextSubscription: Subscription;

    constructor() { }

    ngOnInit() {
        // I assume here that you declared an observable called "someObservable"

        this.myTextSubscription = someObservable$.subscribe(text => this.myText = text);
    }

    ngOnDestroy() {
        // as our observable is not coming from router or http, we need to manually
        // unsubscribe in order to avoid memory leaks

        this.myTextSubscription.unsubscribe();
    }

}

EDIT 1: With your updated example, here's how you should use it with @ngrx :

@Component({
  selector: '...',
  templateUrl: '...html',
  styleUrls: ['...css']
})
export class AppComponent implements OnInit, OnDestroy {
  private text: string;
  private textSub: Subscription;

  constructor(private store$: Store<State>) {
    this.textSub =
        store$.select('yourState')
            .subscribe((state: State) => this.text = state);
  }

  ngOnDestroy() {
    this.textSub.unsubscribe();
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM