Create Modular Components with Angular Structural Directives
When creating components, particularly core components, our goal is to make them as modular and as flexible and grant the consumers a high level of control over what they can pass.
For example, let’s say we build a generic error component. What we want is to give our consumers the flexibly to create it by using one of three options:
- They can choose to use the default text value
- They can choose to use their own text which can be static or dynamic
- They can choose to pass their own template
Let’s create the error component in question, and see how we can meet the above requirements:
We have an error
input that accepts either a string
or a reference to a template
. We also define a default error message in case the consumer doesn’t pass anything.
Additionally, we have the isTemplate
getter, which performs a simple check to let us know whether we need to render a custom template; It’s as simple as that.
Now we can use the component whichever way we need:
So far so good, but our inner code still isn’t reusable, and we need to repeat it for any component that requires this kind of functionality. A better approach would be to create a custom structural directive, that ensures our code is DRY and reusable.
We want to be able to write something like the following:
Much cleaner. Let’s look at the directive’s implementation:
The directive is pretty straightforward; If the type of the input
value is TemplateRef
we render it; otherwise, we render the default template, which in our example is: <p>{{error}}</p>
which can display the default error message or a custom one.
If you’re not familiar with Angular structural directives, I recommend the following resources:
As you can see, the template option requires creating a local variable and passing it to the directive’s input. We can also add support for the following variation:
We do so by using the ContentChild
decorator to obtain a reference to the template:
Now we can truly support any method that fits our consumers’ needs.
What About ng-content?
There are a few reasons why I prefer this way over using ng-content
:
ng-content
makes it harder to support a default content. Yes, you can find workarounds to do it, like checking thechildNodes.length
or by using the CSS :empty
selector, but withng-template
it’s more elegant.- In some cases,
ng-content
can lead to unexpected side-effects, as the the host doesn’t have control over the content. For example, a lazy content (*ngIf
) will not prevent Angular from creating the component instances. You can read more about it here. - While we didn’t use this feature in our example,
ng-template
is more powerful, as it gives us the option to pass a context.
Testing Our Directive with Spectator
It’s important to test our directive, and ensure it works as expected for all the input variations.
Let’s test the directive with Spectator. If you’re not familiar with Spectator, it helps you get rid of all the boilerplate grunt work, leaving you with readable, sleek and streamlined unit tests.
First, we need to create our two test components — the first uses our directive and the second consumes the first.
Now, we can use Spectator to test the various ways the directive can be utilised.
If you want to learn more about Spectator you can read about it in this article by Inbal Sinai:
🔥 Last but Not Least, Have you Heard of Akita?
Akita is a state management pattern that we’ve developed here in Datorama. It’s been successfully used in a big data production environment, and we’re continually adding features to it.
Akita encourages simplicity. It saves you the hassle of creating boilerplate code and offers powerful tools with a moderate learning curve, suitable for both experienced and inexperienced developers alike.
I highly recommend checking it out.
Follow me on Medium or Twitter to read more about Angular, Akita and JS!