Home > Software design >  Correct way to show ngIf when stream is currently null
Correct way to show ngIf when stream is currently null

Time:08-19

I have a stream which on page load equals nothing:

specificPokemonDetail$ = of({}) as Observable<Detail>;

But then later after the user has clicked on a button an api is called and some data is set to it:

this.specificPokemonDetail$ = this.httpService.getPokemonDetail(pokemonUrl).pipe(tap(this.scrollPage));

My question is to do with the templating. My current template shows this:

  <ng-container *ngIf="(specificPokemonDetail$ | async) as detail">
    <div id="detail">
      <div ><img [src]="detail?.sprites?.front_default" [title]="detail?.name | titlecase" /></div>
      <div >
        <div >Name: {{detail?.name | titlecase}}</div>
      </div>
  </div>
</ng-container>

However *ngIf="(specificPokemonDetail$ | async) as detail" causes this container to show on initial page load. What can I change to only show this once the object actually has data in?

CodePudding user response:

I got the answer, of can be empty. So this is the answer:

specificPokemonDetail$ = of() as Observable<Detail>;

CodePudding user response:

If your initial state {} is not meaningful, then you should not emit it!

Let's create an observable that will not emit until there is meaningful data.

We can use a Subject to pass in some param, like maybe a pokemonId that would be used to fetch a record and use that as a starting point to define our specificPokemonDetail$:

private pokemonId$ = new Subject<number>();

specificPokemonDetail$ = this.pokemonId$.pipe(
    switchMap(id => this.httpService.getPokemonDetail(id))
);

setPokemonId(id: number) {
    this.pokemonId$.next(id);
}

If pokemonId$ never emits, then specificPokemonDetail$ will never emit, which is great for your template; it will either receive no data, or valid data, but no meaningless initial state.

Explanation:

  • Subject is an object that allows you to emit values by calling its next method. It also allows you to subscribe to it as on observable. So you can use any of the available rxjs operators on it, just like a normal observable.
  • switchMap - is a fancy little "Higher order mapping operator" that basically subscribes to an observable (in this case, the call to get your pokemon detail) and emits its emissions. If switchMap receives a new emission, it will unsubscribe from its previous "inner observable" and subscribe to the new one.

Here's a little StackBlitz example.


I would suggest not reassigning references to observables. If an observable has some subscribers and you reassign the observable, those subscriptions still point to the old source. And these old subscriptions are not automatically unsubscribed. (switchMap will automatically unsubscribe for you).

In this simplistic example where the source observable only emits a single value and there is only a single subscriber, it works okay. But if the situation gets more complicated (either more emissions per source, or multiple subscribers), the practice of reassigning observables could become a headache.

  • Related