Reducing callbacks improves the experience of working with async logic. With Angular 2 and the async pipe, we can significantly reduce the number (and/or depth) of callbacks we need to make. Let's consider a simple example.
Let's say we're writing an application that fetches articles and displays them to the user. This fictional app requires us to call an API to fetch the articles from a backend service. This usually looks something like this.
ngOnInit() {
this.articleService.getArticles().then(articles => this.articles = articles);
}
You've likely seen this many times before. We're calling a service that returns a Promise
and we're using the then
callback to access the results. When our then
function is called we simply set the component's articles
property to the value passed to our then
function. The template to render the articles looks like this:
<div *ngFor="let article of articles">
{{ article.name }}
</div>
Overall this wasn't too bad, but we did need to use a callback to grab the articles and then assign those to the component's articles
property. This is unneccessary with the async
pipe.
The Async Pipe
We can improve the previous example using the async
pipe. The async
pipe takes an asynchronous reference, waits for the asynchronous operation to complete, and then extracts and returns the result to the template.
With the async
pipe we can remove the callback from the previous code:
ngOnInit() {
this.articles = this.articleService.getArticles();
}
That's much better. We've reduced the noise and can now clearly see the intent. However our articles
property now contains a Promise
not an Array
so our template won't render it correctly. We need to update the template and use the async
pipe, like this:
<div *ngFor="let article of articles | async">
{{ article.name }}
</div>
Can you see the difference? We've made one change to the template, we added the async
pipe to the *ngFor
expression.
Advanced Example
What if we wanted to filter that async collection of articles? Let's say our requirements change, and this component now has to show read and unread articles. We might be tempted to implement it like this:
ngOnInit() {
this.articleService.getArticles().then(articles => {
this.readArticles = articles.filter((article) => article.read);
this.unreadArticles = articles.filter((article) => !article.read);
};
}
This is similar to the first example we saw. Like the first example, we don't need the async
pipe but we do need a callback so that we can filter the array before setting the properties on the component. Here's an alternative syntax to achieve the same thing:
ngOnInit() {
const articles = this.articleService.getArticles();
this.readArticles = articles.then((articles) => articles.filter((article) => article.read));
this.unreadArticles = articles.then((articles) => articles.filter((article) => !article.read));
}
Okay, we've actually increased the number of callbacks here, but we've reduced the depth of the code, we're left aligned as much as possible (we're not building pyramids). Personally, I prefer this syntax. It also works with RxJS
which is useful for more complicated scenarios.
Like the first async example, we also need to use the async
pipe in our template to render the articles:
<h1>Read</h1>
<div *ngFor="let article of readArticles | async">
{{ article.name }}
</div>
<h1>Unread</h1>
<div *ngFor="let article of unreadArticles | async">
{{ article.name }}
</div>
Conclusion
The point of all this is that we can improve how we handle async logic. We can reduce the number (and/or depth) of callbacks we need to write by using the async
pipe. This gives a better developer experience and, in my opinion, in most cases provides cleaner code that expresses it's intent more clearly.