The Issue
Let’s imagine we’ve an observable which fetches a list of users:
@Component({
// Metadata
})
export class AppComponent implements OnInit {
public users$: Rx.Observable<any>;
ngOnInit() {
this.users$ = Rx.Observable.of([
{ id: 1111, name: 'User 1', email: 'u1@dummy.com' },
{ id: 2222, name: 'User 2', email: 'u2@dummy.com' },
{ id: 3333, name: 'User 3', email: 'u3@dummy.com' }
]);
}
}
All right. Suppose we got a mission to display each user field by a separated list. But there is a constraint, each list will be displayed differently. Thus, the appropriate template could be:
IDs:
<ul>
<li *ngFor="let user of users$ | async">{{ user.id }}</li>
</ul>
Names:
<ol type="1">
<li *ngFor="let user of users$ | async">{{ user.name }}</li>
</ol>
Emails:
<ol type="A">
<li *ngFor="let user of users$ | async">{{ user.email }}</li>
</ol>
Let’s check how many subscriptions would be created in that way by adding a log when a subscription is created:
this.users$ = Rx.Observable.of([...])
.do(() => console.info('Subscription is created')):
Here are the results of the Console:
Well, we notice that each usage of async pipe creates another subscription. Although these are three different *ngFor
, we’re talking about the same observable – so we’d expect that async pipe would use a single instance of a shared subscription.
In case there is an operator on the observable, for example, a map
operator which does a heavy business logic like calculating – that operator will be executed for each subscription individually.
Let’s see how to create a single subscription for users$
but still be able to use its result for each list.
The Solution
As part of Angular 4.0.0 – we introduced with the as
keyword. That keyword enables assigning a local variable in the component’s template. In other words, it means we’re able to use a variable (as much as we want) which points to the evaluated result of the async pipe.
Note: In general, assigning the result using the as
keyword isn’t necessarily for the async pipe but for all pipes.
To bind a local variable with the template, we need to wrap the template with the ng-container
directive.
In case that you’re not familiar with the ng-container
– it’s just a wrapper that enables to group multiple DOM elements (without adding an additional element to the DOM) and apply a structural directive on these elements (such as *ngFor
and *ngIf
).
It’s time to use the as
keyword:
<ng-container *ngIf="users$ | async as users">
IDs:
<ul>
<li *ngFor="let user of users">{{ user.id }}</li>
</ul>
Names:
<ol type="1">
<li *ngFor="let user of users">{{ user.name }}</li>
</ol>
Emails:
<ol type="A">
<li *ngFor="let user of users">{{ user.email }}</li>
</ol>
</ng-container>
Basically, what we do is to assign the result from the subscription as a local variable which is named users
. Notice we need a structural directive in order to interpret the as
keyword and for binding the users
variable with the result from the async pipe. That’s the reason we use *ngIf
.
If we’d like to use the users array for other purposes (for instance, printing its length) we could use the users
variable as well – as long as it’s evaluated inside that ng-container
scope.
Here’s attached the final result:
Indeed, we’re done.