Use Angular directives to extend components that you don't own
This post is also available in:
Angular directives are underused and I think this is because we don't know what they're capable of.
If you're using Angular you probably are familiar with the popular structural directives
*ngFor, but does your codebase contain custom directives? The answer to that question is probably no, and it could be that you've resorted to components instead of directives because these are more familiar.
In this blog post, I want to show you a useful technique that uses directives to configure 3rd party components in a unified way. I find this an elegant solution in comparison to creating wrapper components.
Let's take a look at an example.
Default directive link
On my current project we're using the component library from PrimeNG and I've seen the following code repeatedly, for every date picker.
This is the markup required to configure the component the way we want it to behave.
If you ask me this is a lot of code that doesn't only pollute the template but also deceives us into thinking that things are more complex than they are.
I can also forget (or I don't know that I have) to add an attribute to a new date picker, and this creates another experience for the user. Lastly, when the component removes, changes, or adds an attribute I could potentially have to change all the
p-datepicker elements in my codebase.
In short, it has an impact on the developers, and on the users.
When we refactor the code by using a directive, the template becomes simple again, and we're sure that we always provide the same experience to the user.
The refactored template looks this.
But how do we go from 14 lines of HTML to just one line (this is how prettier formats it)? The answer is a directive.
The directive uses the
p-calender component selector to be applied to all calendar elements.
Calendar instance is injected into the directive and is be configured to our needs.
Override the default implementation link
The directive provides a solid base that applies to all date picker elements. But for those one-off cases, it's possible to overwrite the predefined values of the directive for elements that require a different configuration.
In the example below, the navigator options are disabled by explicitly setting their values to
Opt-in directive link
Instead of a directive that changes the behavior of all elements, we modify the selector to target specific elements that have a distinct use case.
For example, for dropdown elements that have a generic contract, the dropdown behavior of those "codes-dropdown" elements can be configured. Notice the added attribute
[codes] of the selector only target codes dropdown elements.
This way only the
p-dropdown elements that have the
codes attribute are configured by the above directive.
To use the above directive in the HTML template we need to add the
codes attribute to the
Opt-out directive link
Another option is to use the
:not() selector for elements that in most of the cases require the same config, but in some rare cases require a one-off configuration. For example, let's say that 90% of the dropdown elements in our application have a data source with "codes". In this case, we don't want to be required to add the
codes attribute to those directives, instead, we want to define when we don't want to use the directive for the remaining 10%.
Instead of using the
codes attribute to mark the codes dropdown, we assume that it's the default behavior but use the
resetDropdown attribute to opt-out of the behavior.
In the HTML template, this translates to the following.
Directives that load data link
We can do more in the directive's implementation. Here we see a directive that populates a dropdown with data, which is useful for data sources that are often used. A variation of this example is to make the data source configurable.
In the example below, we add a
[countries] attribute so that we can bind the directive to specific dropdowns to use a list of countries as the data source. This directive can be used together with the other dropdown directives.
The directive also includes an
@Output emitter when the countries are loaded.
Angular Directives are great, but sadly underused.
Directives are what the Open-Closed Principle is about. The component is closed for modifications, but a directive allows you to extend the component without changing the internals.
For example, with directives, we can change the behavior of 3rd party libraries or an in-house component library without having access to the code of the component.
We could accomplish the same result with wrapper components and with components that have a rich set of configuration options but this requires more code and is harder to maintain.
To target elements that require a different configuration, we can leverage selectors and target the specific elements. Because directives can be stacked we can limit the responsibility of the directive so that it only does one thing.
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.