Angular: arrrgh... too many async bindings
Redundant async bindings will trigger excess invocations of your observable. This simple technique, lets you remove those excess subscriptions
In my [previous post]({{ site.baseurl }}{% post_url 2018-03-26-rxjs-combinelatest-startwith %}) I came across a quirk with Angular, Observables and the Async pipe. That is, each subscription to an observable will initiate the observables execution. Hence we were calling the api once per async subscription.
A subscribe call is simply a way to start an “Observable execution” and deliver values or events to an Observer of that execution.http://reactivex.io
Original version
<p>{{ (result$ | async).name }}</p>
<p>{{ (result$ | async).surname }}</p>
result$ = this.http.get<Person>('/api/person/1');
My original attempt to resolve this, was to first subscribe to the observable in the typescript file, and then bind directly to the results property.
Attempt 1
<p>{{ result.name }}</p>
<p>{{ result.surname }}</p>
result: Person;
this.http.get<Person>('/api/person/1').
subscribe(person => this.result = person);
However, this now raises some more problems for us.
- We get the following error: ERROR TypeError: Cannot read property ‘name’ of undefined. That is, before the http request returns and we set the result property, our bindings are throwing an error.
- For every subscription to an observable, we need to manage the unsubscription. Normally Async pipes handle this for us.
To solve the first problem we have two options. We need to either: a) perform a null check on each binding
Attempt 2.1
<div>
<p>{{ result?.name }}</p>
<p>{{ result?.surname }}</p>
</div>
or b) wrap the bindings in a div and hide it with an *ngIf
Attempt 2.2
<div *ngIf="result">
<p>{{ result.name }}</p>;
<p>{{ result.surname }}</p>
</div>
Neither of these options help us with out second problem, the need to unsubscribe for each subscription. Enter this post from Todd Motto that I came across today, with a neat solution to both of the above problems.
Attempt 3
<div *ngIf="result$ | async as result">
<p>{{ result.name }}</p>
<p>{{ result.surname }}</p>
</div>
Todd then offers a neat little progression to the above, to show an alternative template, when the above div is hidden
Final solution
<div *ngIf="result$ | async as result; else loading">
<p>{{ result.name }}</p>
<p>{{ result.surname }}</p>
</div>
<ng-template #loading><p>Loading Person...</p></ng-template>
Thanks, Todd, this simple hint will help tidy up a lot of my code.