Composing Data Streams to Yield a Single Result and Leveraging the Async Pipe
Similar to the previous posts, we want to combine streams to retrieve from multiple entities. This time however, we make sure that the final product of our stream is our view model.
For this example we keep it rather simple and combine the request for a user object with the request for a list of roles in our system, so we can fill the view with a nicer representation than just a role id.
// Stream that emits an user object when the userId route param changes
const user$ = this.route.params.pipe(
switchMap(({ userId }) => requestUser(userId)),
);
// Stream that emits a list of all role objects once
const roles$ = requestRoles();
// Combine both streams
const userAndRoles$ = combineLatest(user$, roles$);
// Transform combined result into UserView model
this.userView$ = userAndRoles$.pipe(
// Create a UserView object containing the user's properties and its roles
map(([user, roles]) => ({
...user,
roles: resolveUserRoles(user, roles)
}))
);
In the implementation above, we create two streams. One to request and emit a new user object when the userId
route param changes, and one to emit a list of all roles once (technically, we could emit live updates of roles, too). We use the combineLatest
operator which combines the latest emit of the given streams into a single emit. In our case this serves two purposes. First, once the user$
emits again, which happens when we change the route accordingly, the same pipeline runs and the view will be updated with the new user and its respective roles. Additionally, we cannot display the view until the roles are received. combineLatest
will hold back on its emit, until both streams have produced a value.
Finally, we can now use the userView$
stream in our template and subscribe to it via the async pipe. This way we do not even have to handle subscriptions. Another advantage of using the async pipe is that we can easily switch our change detection strategy to OnPush
as the template will only need to be updated when our stream emits.
...
You can see this live on stackblitz.
https://stackblitz.com/edit/angular-ci9trw?file=src/app/app.component.ts
Conclusion
In this series so far, we briefly discussed how combining our streams to yield a single result helps us eradicate potential side-effects. We now illustrated how following this principle can make working with streams in Angular applications easier, as the async
pipe implicitly handles subscriptions for us. Ultimately, this comes with performance improvements, as we can make use of the OnPush
change detection strategy.