Pipes and Observables

Let’s learn why pipes are useful when handling `Observables` in the template.

Asynchronous code in Angular is almost an inseparable technique used in this framework. So, the ability to handle it is crucial. When do asynchronous calls appear? The most common scenario is when we have to send an HTTP request to display some data.

Observables in Angular

Let’s get back to the profile card task we implemented in the last tasks. The data context was simplified a bit compared to a standard application in which the user data needs to be fetched from an API before it’s displayed.

If we want the data context to be simplified, user data will no longer be supplied as a simple data object. Instead, it’ll be retrieved through another service. Because the fetch is asynchronous, the output of the service call will be an Observable User rather than a User object. Let’s look at the example below:

export class AppComponent implements OnInit{

  user$: Observable<User>;

  constructor(
    private userService: UserService,
  ) {
  }

  ngOnInit() {
    // get user from the service (as Observable)
    this.user$ = this.userService.getUser();
  }
}

The user$ property is Observable, so we can’t simply use it to display the data in the template. Instead, we have to subscribe to the asynchronous data that finally comes and use that value to display the profile card.

The obvious way to do this is to create an additional property called user and assign the value once Observable emits it. The solution could look like this:

 export class AppComponent implements OnInit{

  user: User;
  user$: Observable<User>;

  constructor(
    private userService: UserService,
  ) {
  }

  ngOnInit() {
    // Get user from the service (as Observable)
    this.user$ = this.userService.getUser();
		
	// Subscribe to get the value
	this.user$.subsribe(userResponse => {			this.user = userResponse;
	});
  }
}

Now, we can use the value of the user to display the profile card, and it will work.

However, this approach has several weaknesses, which are listed below:

  • It increases the state of the component.

  • The code is not very readable. There are a lot of lines and properties.

  • There may be memory leaks if we do not handle subscriptions correctly.

Therefore, we’ll accept the help of a built-in pipe to better handle Observables.

The async pipe

Angular introduces a pipe called AsyncPipe precisely for cases where we have an Observable value that we want to use in the template to render the data once the it returns. So, we have to remember that the data will come eventually when we use Observables, but we have no control over when it happens. That’s why we need to handle it using AsyncPipe.

The AsyncPipe syntax is fairly straightforward. The pipe’s name is just async, which we can see below:

Observable | async

Let’s assume that our User is defined like this:

interface User {
  name: string;
  lastname: string;
}

To display its name, we need to handle the Observable first and then get the name from the data.

<p> {{ ( user$ | async ).name }} </p>

The ( user$ | async ) fragment passes Observable to AsyncPipe, resolving the value (which is a user object) once it comes. Under the hood, it makes a subscription, just like we did in our component at the beginning of this lesson. However, the pipe takes excellent care and unsubscribes from it when the component is destroyed to prevent memory leaks.

Now, let’s think of what would happen if we also want to display the lastname field below the name:

<p> {{ ( user$ | async ).name }} </p>
<p> {{ ( user$ | async ).lastname }} </p>

It renders the data correctly. However, there are two subscriptions for the created user, which result in two sent HTTP GET requests. This is not ideal.

The as keyword

The best option is still to use AsyncPipe but call it only once, to prevent multiple subscriptions. Our well-known NgIf directive has a powerful feature that can help us exactly in this case. Basically, when we use NgIf in the template, we can not only check the statement for the boolean value, but also assign the value to a template variable. This is available through the as keyword. Let’s take a look at this syntax:

<div *ngIf="someData as data"> 
	{{ data.value }}
</div>

In this example, the div element is displayed when the someData object has a true value. But that’s not all! The value of the someData object is assigned to the data template variable. Therefore, this variable can be used in the template as {{ data.value }}.

In the above case, it doesn’t make much sense to use this technique. However, using the as keyword can be a powerful technique when it comes to the Observable value. We can use NgIf to assess the Observable value resolved by AsyncPipe as true, and then use the result as a template variable. Let’s look at the code below:

<ng-container *ngIf="user$ | async as user">
	<p> {{ user.name }} </p>
	<p> {{ user.lastname }} </p>
</ng-container>

The NgIf directive resolves the asynchronous value with the AsyncPipe and assigns it to the variable called user. This variable is then gets the value of the user. If we do this, we can forget about all asynchronous issues because it’s a regular value now!

Below is the complete example of how this technique works:

Get hands-on with 1400+ tech skills courses.