Leveraging @angular 15 host directives
Tags: angular, webdev, typescript
Published: 2023-01-26 10:27:33

Not while ago, Angular team released a stable 15 version with few neat features, such as host directives.
In this article I will try to explain how to leverage the directive composition approach and move from old class-inheritance to a composition approach.

## What are host directives?
Host directives are a **standalone** directives that can be added to a component via `@Component` decorator, thus avoiding the need to apply the directive through the markup. Developers can expose its inputs and outputs. Additionally, they can also map their names to avoid the confusion between components and directives properties.

## Why would you want to use host directives?
With a complex component comes complex business logic inside its class. Typescript has [mixins](https://www.typescriptlang.org/docs/handbook/mixins.html) support to divide logic between multiple classes and then join it into one giant class.

Mixins are widely used in @angular/material project. For example, [Material Button Component](https://github.com/angular/components/blob/9f0071dc4c4848ea440907e016a12a5cba48a1a9/src/material/button/button-base.ts#L81).
But, as you can see, it itself requires complex structures to actually use it. Not mentioning setting inputs/outputs properties as an array for the decorator itself.
In short, developers can start struggling with input/output properties and class dependencies if they use lots of mixins.

Another way is to use long chains of **class inheritance**.

In the end, the final component would have a huge constructor (prior to `inject` function) and supporting this constructor sometimes becomes too painful.

Another way would be to use **services** injected into your component, but this creates an additional headache with keeping the config up-to-date (triggering some configuration update for the service when some component's input property was changed, etc.).

Directive composition approach works similarly to the Typescript's mixins: you have multiple classes that contain their own logic, and in the end they all end up used for one final class (component). The difference is that mixins are combined into one class, and for directive composition you need to inject your directive instances into your component class.

## Leveraging the host directives in your application

First, I'll [leave a link to an example app](https://github.com/N1XUS/host-directives-app) built with nx to split the app and its libs.

Let's take an example of simple form control component:

{% embed https://gist.github.com/N1XUS/4da4768eddc51f923c13fab66b9a0d2c %}

This component implements `ControlValueAccessor` and implements its methods such as `writeValue`, `registerOnChange`, `registerOnTouched`.
And this stuff is commonly repeated across multiple components in your app.
Previously, to simplify the logic you could extract those methods into base abstract class. But this class might require it's dependencies, which needed to be passed via `super` call in constructor. This complicates things.

Let's simplify the code, and first, create a standalone directive called `CvaDirective`.

{% embed https://gist.github.com/N1XUS/75ae36d6f3becdbcba13e7a4a677d9eb %}

Let me explain what's going on inside this directive.
First, we are declaring it as a standalone, which means we can apply it to a component via `hostDirectives` property of the `@Component`.
Next, since we need to support template-driven and reactive forms, lets' inject necessary dependencies such as `NgControl`, `NgForm` and `ControlContainer`. We will need these properties later.

You may see that we also injected `ChangeDetectorRef` from the `host`. This is needed to get the components change detector and call it when the state of the control being changed (valid/invalid).

Next, we implement all members of `ControlValueAccessor` interface for further usage in the component.
We also have support for a disabled state of the form control, which may be handy in real-case scenarios. This input property is optional and you can ignore it during input exposing.

We also have `updateErrorState` method which automatically checks whether the control is valid and checks whether the user interacted with the control itself or submitted the form.

That's all for the directive itself, now let's update our combobox component to use this directive instead direct `ControlValueAccessor` implementation:

{% embed https://gist.github.com/N1XUS/d3971a875f9e4901682dd402e36936ec %}

So, we've removed all form-related stuff to the directive. This gives us a more clear and readable component.

You see that we've injected `CvaDirective` into the component to call its members such as `setValue` and use the initial value of the form control to set it for the input field.

## Bonus: Advanced Directives composition
With the example above I've shown how to simplify your component and move all background logic into a separate class without the need of inheritance.

Now, let's say we want it to accept not only `string[]`, but also `Observable<string[]>`, or even custom data source class which retrieves the data from some backend.

And again, host directives to the rescue!

Before we start with the directive itself, let's define what our directive should support:

- Automatically subscribe/unsubscribe from the dataSource;
- When datasource's data changes, or new dataSource instance being passed, notify parent component of the changes in data;

In this example, we will create a simple data source class which will convert passed data into an observable and simply return it.

First, let's generate abstract data source provider class which our components would implement in own way:

{% embed https://gist.github.com/N1XUS/166e360f870a61c93196e139aa606eb7 %}

So, here we're declaring that our `DataSource` can accept three types of data: Class instance, Observable of an array and a plain array.

With `AbstractDataSourceProvider` we can now actually create our directive called `DataSourceDirective`:

{% embed https://gist.github.com/N1XUS/3892e213bc17686522db5f2111345abb %}
Quick explanation of what's going on here:
We have `T` and `P` generic types which are responsible for providing awareness of the data types we are working with in our components, so IDE also knows it, and provides suggestions.

Next, we have a `dataSource` input property which accepts our `DataSource` type.

When the data is set, we call `_initializeDataSource` method which does couple of things:
First, it closes the stream of the previous data source.
Then, it transforms our data into acceptable data source provider with the help of our `DataSourceParser` which is injected with a `DATA_SOURCE_TRANSFORMER` injection token.
Lastly, it subscribes to the events of the data source provider and passes them to the component it applied to.

That's all for the directive itself and its dependencies.

Now, let's go back to our combobox component, and update it in order to accept multiple types of data.

First, we need to implement our `AbstractDataSourceProvider` class:
{% embed https://gist.github.com/N1XUS/01e86a666d7b2f5e14f98e515290c5ba %}

As you can see, we defined an additional interface for the combobox item in case we want to render the label different than its value.
And for the data source provider, we are just checking whether the data is observable or a plain array. If it's an array, we wrap it into Observable and return it.

Additionally, we are implementing `DataSourceParser` for combobox to be able to apply the necessary data source class for the data passed to it.

Now, let's update our component to work with the data source directive:

{% embed https://gist.github.com/N1XUS/52beaf9fc80198ac45fb850015968d8f %}

So, what's changed?

First, we added our DataSourceDirective to hostDirectives and exposed its `dataSource` input property as `options` input property which we previously had directly in the component.

Next, instead of relying directly on `options` input property, we're subscribing to DataSourceDirective's `dataChanged$` `BehaviourSubject` and waiting for new data to come.
When the data is emitted, we update the inner `options` property with the data received from the DataSourceProvider.

And that's pretty much it!

In conclusion: Even though Host Directives at the early stage of its development, and has some childish issues, such as explicit definition of the host directive, it already provides huge benefit of simplifying the codebase of your existing components and libraries by splitting the logic between multiple independent classes and reducing the amount of inheritance chains.

As I was mentioning at the beginning of the article, here's a complete [example application](https://github.com/N1XUS/host-directives-app) used in this article.