Directive Composition API

In the past few weeks, we’ve covered different examples of directives, such as the CDK Listbox directive and the CDK Drag and Drop directive. I have mentioned that directives are underused in Angular applications these days, and one way to use more directives is to adopt the directive composition API.

Let’s consider our previous example of a list of items:

Let’s assume we want to make our list flexible enough to support re-ordering items using drag and drop. To do so, we could start using multiple different directives on the same element, such as cdkOption and cdkDrag (code example here on Stackblitz):

While the above code works, it’s designed for a one-time use case. If we know that our application will use several droplists that support drag-and-drop, we should start thinking about creating our own custom directive that refactors these different features in one place. Enter the directive composition API:

This new directive draggableOption is composed of both cdkOption and cdkDrag to achieve the desired result. We have one input text that is forwarded to the cdkOption input thanks to this syntax:

Note that both inputs and outputs can be forwarded that way (code examples here). The beauty of this approach is that our new directive has very little code while packing reusable features in a concise syntax. This is how we would use our new directive:

You can find that example live on Stackblitz here. There are a few caveats with the directive composition API, mainly that it only works with standalone directives and that the options to “forward” inputs and outputs are limited for now. However, the composition API is an excellent option to make our code more reusable and immune to copying and pasting collections of directives from one component to another.

Introduction to State Management

When you work with Angular for quite some time, you realize that you implement similar services repeatedly. Most likely, you end up with services with BehaviorSubjects to store some data, and you have components that subscribe to these subjects exposed as read-only Observables.

If you’re not at that point yet, I suggest you stop reading this article and get into the above architecture pattern first. This is a good reading about what I call “progressive state management,” where you get into state management little by little. Start there.

Once comfortable with the above, you might start thinking about NgRx, NgXs, or other state management libraries.

Why state management?

The main goal of a state management library is to define a single way to handle data in your application. Instead of having your application data in multiple different services, all your data will belong in a single, massive object called state. That state has to follow several rules and cannot be updated directly.

State management is all about rules and conventions created by the ancestor of all state management libraries: Redux. All modern libraries use similar concepts and vocabulary.

Here are the three core principles of Redux:

  • Single source of truth: The app’s state is stored in a single object tree within a single store. In Angular applications, that store is usually a service that can be injected anywhere we want.
  • The state is read-only: The only way to change the state is to emit an action, an object describing what happened and how we want to change our state.
  • State changes are made with pure functions called reducers. These functions take the previous state, an action, and return the next state. They return new state objects instead of mutating the previous state.

Visually, a typical Redux application works like this: A user interaction triggers an action that updates the global state using a reducer, and then components receive state updates to render the new data:

I know what you’re thinking at that point: That’s a lot of definitions and vocabulary. But this is what all state management libraries give us, so there’s no way around it. For instance, here are the links to these concepts in 3 different state management libraries:

  • NgRx: State (notice how that class extends BehaviorSubject) – ActionReducer.
  • NgXs: StateActionReducer (note that NgXs does not mention reducers directly and tries to simplify how they work, which is one of the reasons why it’s my state management library of choice)
  • Redux: StateActionReducer

We’ll dive into these topics in more detail with a series of posts in the next few days/weeks.

ngOnDestroy lifecycle hook

After covering ngOnChanges and ngOnInit earlier, let’s talk about ngOnDestroy. The ngOnDestroy lifecycle hook is called when an Angular component/directive/pipe is destroyed. I’ll refer to components only in this post, but everything you read also applies to directives and pipes.

This can happen when the component is removed from the DOM, or the application is destroyed.

Most of the time, a component gets destroyed when it’s removed from the DOM as a result of:

  • A ngIf directive condition becomes false, thus hiding the element from the screen.
  • The router navigates to a different screen, thus removing the component from the DOM.

The ngOnDestroy hook is a good place to do cleanup tasks, such as unsubscribing from Observables (though there are better options to automatically unsubscribe your Observables), closing sockets, or canceling setInterval and setTimeout tasks (though you could use RxJs timer for that):

What you need to know about ngModules

Angular modules (ngModules) are a source of confusion for Angular developers. This short guide will clarify what they are and how to think about ngModules in general.

Why ngModules?

Angular uses a Typescript compiler and a template compiler that turns our HTML templates into Javascript instructions to render the DOM in a browser.

The Typescript compiler knows how to compile our Typescript code (all our classes) because all dependencies have to be imported in these Typescript files:

Compiling the HTML template for the above component is a different task because *ngIf is not imported anywhere in our Typescript code. Yet, the Angular compiler must know about all directive/component/pipe dependencies of a component’s template to turn all that HTML into DOM instructions.

The solution to that problem was the introduction of ngModules. ngModules expose features meant to be used by the template compiler and does so in a “global” way, in the sense that importing an ngModule once is enough to enable all its code for all components within that module.

That’s why we rarely think twice about using ngFor or ngIf or any pipe: They are all part of CommonModule, which is automatically imported into the AppModule by default:

Do I need to create ngModules in my app?

Most likely, no. The Angular team introduced standalone components as an alternative to ngModules. A standalone component does not need such modules because all its dependencies are listed in the Typescript code itself:

There were only two reasons why you’d need to create your own ngModules in the past:

That’s it. Both of these problems are solved by standalone components, which can be lazily loaded and already bring all their dependencies along, so no ngModule is needed.

What about core, shared, and feature modules?

Those were parts of guidelines created for the sake of consistency within Angular applications. But you don’t need these modules for your application to work. You can still organize your code neatly in folders and sub-folders and not use ngModules. You can even have tidy, short import statements without having ngModules.

Usually, the more ngModules you create, the more work and problems you’re likely to encounter (circular dependencies, anyone?), which is why the Angular team introduced standalone components and already migrated all its internal directives and pipes to standalone. In the long run, ngModules will likely disappear.

What is ng-container?

Yesterday, we covered ng-template and touched on different scenarios when it makes sense to use ng-template. Let’s now introduce a close relative of ng-template called ng-container.

There are a lot of similarities between those two elements, which can both be used to use structural directives such as ngIf or ngFor, with the bonus that ng-container can be used with the shorthand syntax of those directives:

One thing you probably noticed as an Angular developer is that we can’t use two structural directives on the same element:

In this scenario, using ng-container is the perfect way to use both directives without adding any HTML to the DOM, as ng-container, just like ng-template, doesn’t create new DOM elements:

Another use for ng-container is to become the host of a ng-template, similar to the way that a router-outlet is the host of routed components:

Such templates can be passed from a parent component using @Input and let the child component decide when to display which template, which is very powerful:

You can find a complete code example of such a feature in this Stackblitz and a more in-depth tutorial here if you want to try it.

Using services to cache data

Yesterday, we saw that services are singletons that are created once for all and stay in memory as long as the application is open.

This makes our services the best place to store data that can be shared between multiple different components. Such services can use BehaviorSubjects or Signals to cache that data and replay the last value automatically to any new subscriber (component, directive, pipe, service, etc.).

Here is an example using a signal to store a value and expose it in a read-only fashion:

This article compares BehaviorSubjects and Signals to cache a shareable value.

How to pass multiple pieces of content from one component to another?

Yesterday, we saw that we could use content projection to pass an HTML template from one component to another. But what if we want to pass multiple different templates? We can use multi-slot content projection.

Here’s what we want to achieve:

In our reusable dialog component, we will have multiple ng-content elements. Each ng-content will have a specific query selecting a template to render:

These queries use the CSS selector syntax. In the above example, we’re using [], which means we are selecting HTML attribute names. As a result, our parent component will pass two distinct templates, one for the body and one for the footer, using the following syntax:

You can see an example of such code in action on Stackblitz.

Passing custom content to a component with content projection

If you’re creating a component library or working on components meant to be as generic and reusable as possible, then content projection should be a major part of your Angular toolkit.

Let’s take the example of a pop-up window/dialog component:

The main idea of content projection is that the parent component (most likely a container component) knows about the current use case of our reusable presentation component and can pass custom HTML to it. Such content is passed in the content section of the component, which is the body of the HTML element – what we have between the opening and the closing tag:

Angular projects that content into the template of the child component. We can set the destination of that projection by using an ng-content element in the template of the reusable component as follows:

Content projection is like cutting and pasting some content from one component to another. You can see the full code example here on Stackblitz.

How to improve performance with pure pipes

Earlier in this newsletter, we saw that calling a method in a component template is an anti-pattern. The antidote to that anti-pattern is to use a pure pipe.

By default, all pipes we use in Angular are pure. Custom pipes are also pure by default. The only way to make a pipe impure is to add the config option pure: false to its decorator:

What is a pure pipe?

A pure pipe is one that Angular will execute only when there is a pure change to its input value. It is automatically optimized for performance since it is executed only when needed.

A pure change is either a change to a primitive input value (such as string, number, or boolean), or a changed object reference (such as Date, Array, or Object).

In other words, if we consider the following use case:

Here are some examples of pure and impure changes in variables:

With all that information, we are now equipped to call a formatting function in a template by using a pipe (said function would be called in the transform method of the pipe). We know that the function would run only when the input value changes (purely), which allows us to decide when we want that pipe to be executed again by making either a pure or an impure change to the input value.

Finally, we can double-check that our pipe is working as expected using the Angular profiler to make sure that the pipe doesn’t run more often than expected.

How to create a generic error handler with Angular?

Earlier in this newsletter, we covered how to handle errors with RxJs Observables. What about errors in the rest of the application? Javascript has a primary try/catch mechanism, but this doesn’t help handle errors in a generic, unified way.

Fortunately enough, Angular has an ErrorHandler interface that can be implemented by global error handler services such as this one:

That way, we can implement a catch-all method to deal with errors and display them to the user consistently (code example here). Even better, such a service could report errors to a server for further investigation and debugging from the dev team.

Here is a link to my custom error-handling tutorial to see a good example of how to implement a ErroHandler service.