Angular Reactive Templates with ngIf and the Async Pipe
The ngIf template syntax proves invaluable across numerous everyday scenarios, such as employing an else clause to present a loading indicator.
However, there's more depth to ngIf than initially meets the eye: the "ngIf as" syntax, when paired with the async pipe, is a cornerstone for crafting templates in a more reactive manner.
Adopting this template style offers several advantages:
- fewer memory leak risks (contingent on the Observable types employed)
- enhanced code readability
- a reduction in potential complications arising from multiple subscriptions at the service layer
- reduced state management within our components
This approach is an excellent strategy for creating more readable templates while proactively circumventing a range of common pitfalls.
Table Of Contents
This post will delve into the following subjects:
- an initial examination of the
ngIfElse syntax - a subsequent look at the "ngIf as" syntax
- an attempt to construct a template in a non-reactive fashion, highlighting potential issues
- a refactoring of that template into a reactive style using
ngIfelse andngIfas, discussing the resulting benefits
Let's dive straight into our discussion on crafting Reactive Angular templates.
NgIf Else Example
First, let's examine the ngIf Else syntax in isolation with an example:
As observed, we can attach an else clause to ngIf, referencing a template by name. This template will replace the element bearing the ngIf directive when the condition evaluates to false.
Depending on the condition's truthiness, this will print either "Condition is true" or the contents of the loading template onto the annotated element.
The ngIf as Syntax
The ngIf directive also permits evaluating an expression's truthiness and assigning the expression's result—which may not be a boolean—to a variable.
Consider this example:
This template would display "Angular For Beginners" because the expression course is truthy. Notice that course refers to a component property holding a plain JavaScript object.
The expression's result isn't a boolean; it's an object. This object gets assigned to a local template variable named result, allowing the description property to be printed as expected.
A less obvious benefit of this syntax is how it significantly simplifies writing our templates in a more Reactive Style.
What is the relation between the ngIf as syntax and Reactive Programming?
To grasp how the `ngIf as` syntax bolsters Angular's built-in reactive programming support, we need a more tangible example involving backend data loading.
We'll attempt to display this data using the async pipe. Imagine a CourseService fetching data asynchronously from a backend via the Angular HTTP module:
Let's also examine the structure of the custom Course type:
It consists of two mandatory properties, id and shortDescription, and three optional properties marked with a question mark.
Suppose our application loads this data and wants to display all course properties. Our component would look like this:
Let's dissect what's happening here:
- we declare an Observable member variable named
courseObs, initially undefined - we initialize
courseObswith an Observable returned from the service layer - we are unaware of when the Observable will emit or what it will emit, only that it should be a
Courseinstance
To display this data, we'd naturally turn to the Angular async pipe.
Why use the async pipe?
It automatically manages subscriptions and unsubscriptions as the component is instantiated or destroyed—a highly beneficial feature.
This is particularly critical for long-lived observables, such as those from the router or AngularFire.
Furthermore, it enhances code readability and declarativeness, reducing state variables within our component classes.
For instance, the component above only holds an observable member variable; we don't have direct data access at the component level—only the template accesses it.
So let's use the async pipe to render the course details:
This approach proves awkward because the async pipe must be used multiple times. This could lead to multiple subscriptions (and thus multiple HTTP requests), potentially causing further issues and harming readability.
In practice, we often resorted to this alternative to sidestep these problems:
Here, we've manually subscribed to the service's observable and assigned the backend result to a local variable. Our template simplifies significantly: just a variable named course to access the data.
However, this approach has potential drawbacks, especially in larger applications.
What are some potential problems with this approach?
While functional, a key concern is the requirement for manual subscription management.
This is inconsequential for HTTP observables, which emit once and complete. But for long-lived observables, it could create issues.
Another potential issue with this approach
Introducing a local variable to pass data to the template makes the program more imperative than the reactive approach of defining an Observable, passing it to the template, and declaring its usage there.
With state now stored in a component-level variable, there's a temptation to write code that mutates this state.
In this small example, it's harmless. But in a larger app, it might lead to maintainability issues.
Our goal was to use the async pipe for a more reactive style, avoiding these issues by design.
Instead, we fell back on manual subscriptions due to the impracticality of using the async pipe directly.
Another alternative - refactor into a smaller component
Is there a better way to avoid using the async pipe multiple times?
One option is to extract the course detail implementation into a separate component. This preserves the async pipe usage and avoids manual component-level subscriptions:
The course-detail component would be:
This is a viable option, especially if we plan to reuse the component elsewhere.
However, we've created an extra component mainly to circumvent multiple async pipe instances in the parent template.
It turns out there's a superior solution leveraging ngIf's features! Let's explore it.
Reactive Style Templates - The ngIf as syntax and the async pipe
By combining all of ngIf's features with the async pipe, we arrive at this solution:
Let's break down this example:
- the async pipe subscribes to
courseObsonly once - the else clause defines a loading state (
ng-templatenamedloading) for when data isn't ready - the 'as' syntax creates a template variable,
course, for the expressioncourseObs | async - this aliased
coursevariable holds the course object emitted by the observable - a local
coursevariable is now available within thengIfsection, corresponding to the backend's emitted value - this
coursevariable is immediately usable, just as if the object was passed synchronously via an@Input()
Advantages of this more reactive approach
Writing templates in this reactive style offers these benefits:
- no manual component-level subscriptions for service layer observables
- no need for extra smaller components just to use the async pipe once and avoid multiple subscriptions
- no local data state variables at the component level, minimizing risks from local state mutation
- a more declarative codebase: components and templates are declarative, connecting data streams rather than storing and passing local variables
We achieve a highly readable template with less code and fewer potential failure points, such as memory leaks on long-running streams—the async pipe handles unsubscriptions transparently!
Additionally, we avoided both repeating the async pipe on the template and creating an intermediate component.
Conclusions
As demonstrated, the ngIf / else features are versatile, but particularly powerful when combined with the async pipe, simplifying Angular development in a more reactive style.
We hope this was insightful. For more on RxJS in Angular, consider the Reactive Angular Course, which covers numerous common reactive patterns for Angular apps.
Subscribe to our newsletter for notifications on future articles:
New to Angular? Check out the Angular for Beginners Course:
Other posts on Angular
If you found this post helpful, you might also enjoy these other popular articles:
- Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
- Angular Router - Extended Guided Tour, Avoid Common Pitfalls
- Angular Components - The Fundamentals
- How to build Angular apps using Observable Data Services - Pitfalls to avoid
- Introduction to Angular Forms - Template Driven vs Model Driven
- Angular ngFor - Learn all Features including trackBy, why is it not only for Arrays?
- Angular Universal In Practice - How to build SEO Friendly Single Page Apps with Angular
- How does Angular Change Detection Really Work?
