@Component({
selector: 'async-pipe',
template: `
<div class="card card-block">
<h4 class="card-title">AsyncPipe</h4>
<p class="card-text" ngNonBindable>{{ promiseData }}</p> (1)
<p class="card-text">{{ promiseData }}</p> (2)
</div>
`
})
class AsyncPipeComponent {
promiseData: string;
constructor() {
this.getPromise().then(v => this.promiseData = v); (3)
}
getPromise() { (4)
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Promise complete!"), 3000);
});
}
}
Async Pipe
Learning Objectives
-
When to use AsyncPipe.
-
How to use AsyncPipe with Promises and also Observables.
Overview
Normally to render the result of a promise or an observable we have to:
-
Wait for a callback
-
Store the result of the callback on a class property
-
Bind to that property in the template.
AsyncPipe lets us uses promises and observables directly in our templates without having to store the result on an intermediate property or variable.
AsyncPipe accepts as argument an observable or a promise and then waits for the asynchronous result from either before passing it through to the caller as a result.
AsyncPipe with promises
Lets first create a component with a promise as a property.
[source,typescript]cript.ts
-
We use
ngNonBindableso we can render out{{ promiseData }}as is without trying to bind to to the propertypromiseData -
We bind to the property
promiseData -
When the promise resolves we store the data onto the
promiseDataproperty -
getPromisereturns a promise which 3 seconds later resolves with the value"Promise complete!"
In the constructor we wait for the promise to resolve and store the result on a property called promiseData on our component and then bind to that property in the template.
Visually we see something like this:
To save time we can use the async pipe in the template and bind to the promise directly, like so:
[source,typescript]cript.ts
@Component({
selector: 'async-pipe',
template: `
<div class="card card-block">
<h4 class="card-title">AsyncPipe</h4>
<p class="card-text" ngNonBindable>{{ promise }}</p>
<p class="card-text">{{ promise | async }}</p> (1)
</div>
`
})
class AsyncPipeComponent {
promise: Promise<string>;
constructor() {
this.promise = this.getPromise(); (2)
}
getPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Promise complete!"), 3000);
});
}
}
-
We pipe the output of our
promiseto theasyncpipe. -
The property
promiseis the actual unresolved promise that gets returned fromgetPromisewithoutthenbeing called on it.
The above results in the same behaviour as before, we just saved ourselves from writing a then callback and storing intermediate data on the component.
AsyncPipe with observables
To demonstrate how this works with observables we first need to setup our component with a simple observable, like so:
import { Observable } from 'rxjs/Rx';
.
.
.
@Component({
selector: 'async-pipe',
template: `
<div class="card card-block">
<h4 class="card-title">AsyncPipe</h4>
<p class="card-text" ngNonBindable>{{ observableData }}
<p class="card-text">{{ observableData }}</p> (1)
</div>
`
})
class AsyncPipeComponent {
observableData: number;
subscription: Object = null;
constructor() {
this.subscribeObservable();
}
getObservable() { (2)
return Observable
.interval(1000)
.take(10)
.map((v) => v * v);
}
subscribeObservable() { (3)
this.subscription = this.getObservable()
.subscribe( v => this.observableData = v);
}
ngOnDestroy() { (4)
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
-
We render the value of
observableDatain our template. -
We create an observable which publishes out a number which increments by one every second then squares that number.
-
We subscribe to the output of this observable chain and store the number on the property
observableData. We also store a reference to the subscription so we can unsubscribe to it later. -
On destruction of the component we unsubscribe from the observable to avoid memory leaks.
Important
Visually we see something like this:
Again by using AsyncPipe we don’t need to perform the subscribe and store any intermediate data on our component, like so:
@Component({
selector: 'async-pipe',
template: `
<div class="card card-block">
<h4 class="card-title">AsyncPipe</h4>
<p class="card-text" ngNonBindable>{{ observable | async }}
<p class="card-text">{{ observable | async }}</p> (1)
</div>
`
})
class AsyncPipeComponent {
observable: Observable<number>;
constructor() {
this.observable = this.getObservable();
}
getObservable() {
return Observable
.interval(1000)
.take(10)
.map((v) => v*v)
}
}
-
We pipe our
observabledirectly to theasyncpipe, it performs a subscription for us and then returns whatever gets passed to it.
By using AsyncPipe we:
1. Don’t need to call subscribe on our observable and store the intermediate data on our component.
2. Don’t need to remember to unsubscribe from the observable when the component is destroyed.
Summary
AsyncPipe is a convenience function which makes rendering data from observables and promises much easier.
For promises it automatically adds a then callback and renders the response.
For Observables it automatically subscribes to the observable, render the output and then also unsubscribes when the component is destroyed so we don’t need to handle the clean up logic ourselves.
That’s it for the built-in pipes, next we will look at creating out own custom pipes.
Listing
import {NgModule, Component, Pipe, OnDestroy} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {Observable} from 'rxjs/Rx';
@Component({
selector: 'async-pipe',
template: `
<div class="card card-block">
<h4 class="card-title">AsyncPipe</h4>
<p class="card-text" ngNonBindable>{{ promise | async }} </p>
<p class="card-text">{{ promise | async }} </p>
<p class="card-text" ngNonBindable>{{ observable | async }} </p>
<p class="card-text">{{ observable | async }}</p>
<p class="card-text" ngNonBindable>{{ observableData }} </p>
<p class="card-text">{{ observableData }}</p>
</div>
`
})
class AsyncPipeComponent implements OnDestroy {
promise: Promise<string>;
observable: Observable<number>;
subscription: Object = null;
observableData: number;
constructor() {
this.promise = this.getPromise();
this.observable = this.getObservable();
this.subscribeObservable();
}
getObservable() {
return Observable
.interval(1000)
.take(10)
.map((v) => v * v);
}
// AsyncPipe subscribes to the observable automatically
subscribeObservable() {
this.subscription = this.getObservable()
.subscribe((v) => this.observableData = v);
}
getPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Promise complete!"), 3000);
});
}
// AsyncPipe unsubscribes from the observable automatically
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
@Component({
selector: 'app',
template: `
<async-pipe></async-pipe>
`
})
class AppComponent {
imageUrl: string = "";
}
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent,
AsyncPipeComponent
],
bootstrap: [AppComponent],
})
class AppModule {
}
platformBrowserDynamic().bootstrapModule(AppModule);