Introduction
With a release cycle every 6 months, the release of Angular 15 is fast approaching.
As a reminder, the version 14 of Angular brought some big new features:
- standalone components
- composition pattern
- typed forms, etc
The standalone components have had a big impact on the entire Angular ecosystem. And this feature still has impacts in version 15.
In this version, a new API will be availble: the directive composition API
The problem
Directives are one of the most important and powerful parts of Angular. There are 3 types of directives:
- structural directives allowing to structure the DOM
- attribute directives allowing you to enrich an element of the DOM
- components which are simple directives without views
A concrete example of a directive could be the MatTooltip directive of Angular Material. This directive allows to display additional information to the user when he hovers over a text for example.
<div matTooltip="More information" matTooltipPosition="left">
This is a text
</div>
Now imagine that you want to create a button that displays additional information when the user hovers over it.
If we take inspiration from the various libraries in Angular, it's a good practice to write an attribute directive that will enrich the classic button
<button type="button" sfeirButton matTooltip="awesome button" matTooltipPosition="left">
Click Me
</button>
This case is too boilerplate. Developers should remember to use the MatTooltip directive and configure it correctly.
Ideally it will be awesome if developers can write someting like this:
<button type="button" sfeirButton tooltipText="awsome button" tooltipPosition="left">
Click Me
</button>
If we want to write this kind of code, we have two choices:
- extend the MatTooltip class, but it's only possible to extend one class, which limits the possibilities of composition
- to realize a mixins pattern present in Vue Js which can be complicated to maintain due to the possible side effects. For information Angular material uses this pattern as we can see here
Solution
To solve this problem, and always with the aim of improving the developer experience, the Angular team introduces a new API called: Directive Composition API.
This API will allow to compose the directives between them in a very simple way.
Now the @Directive and @Component decorator take a new property called hostDirectives.
This property takes as value an array of standalone directives or an array of configuration objects
@Directive({
selector: "button[sfeirButton]"
hostDirectives: [
AwesomeDirective,
{ directive: MatTooltip,
inputs: [],
outputs: []
}
]
})
export class SfeirButton
By default none of the inputs or outputs of the host directives will be available on the host, unless they are specified in the inputs or outputs properties.
If you want to expose one of the inputs or outputs of the host directives, you must specify it respectively in the inputs and outputs array.
@Directive({
selector: "button[sfeirButton]"
hostDirectives: [
{ directive: MatTooltip,
inputs: ['matTooltip', 'matTooltipPosition'],
outputs: []
}
]
})
export class SfeirButton
The API also allows you to rename the exposed inputs and outputs
@Directive({
selector: "button[sfeirButton]"
hostDirectives: [
{ directive: MatTooltip,
inputs: [
'matTooltip: tooltipText',
'matTooltipPosition: tooltipPosition
],
outputs: []
}
]
})
export class SfeirButton
Thanks to this API, and with this way, it's possible now to write:
<button type="button" sfeirButton tooltipText="awsome button" tooltipPosition="left">
Click Me
</button>
Obviously this syntax is also valid for the outputs
One last important thing, the host directives are picked by view/content queries as well, so it's possible to write:
@ViewChild(MatToolTip) matToolTip!: MatTooltip
Conclusion
With version 15 and the new API, it becomes very easy to compose directives together.
To compose directives between them, you just have to use the new hostDirective property.
Be careful to compose directives between them, all directives must be of type standalone.
Top comments (9)
When composing inputs I have an error on the HTML.
Is it the IDE or Angular complaining about: Unknown property:
By default all inputs or outputs of your host directives are not exposed.
Is this input exposed ? Which IDE do you use ? Vscode ?
I am using webstorm 2022.3. You might be right about the exposing part.
If i re-declare the IO at the level of the Component it stops complaining
Normally, if you expose your inputs and outputs in the property inputs or outputs of the hostDirective as in the example in the article, there is no error in VsCode for example.
The angular plugin of Webstom is maybe not up to date with the last version of Angular and the directive composition feature. I know that Jetbrains recreate the Angular plugin from scratch to have better refactoring than the Angular plugin for Vscode. This have as impacts that sometimes, the plugin is not up to date when the last version of Angular is out
In order to remove the error I have to kind of duplicate the declaration, It's also the only way I get to use the value form the directive. I am trying to see if there is a way to access the HostDirective , I think it would be simpler, but @ViewChild does not seem to work well.
I have found this uniq video whit more details
youtube.com/watch?v=oC9Qd9yw3pE
Yup definitely a gem, 😂. This Video should be the official docs.
Hey, in the following you can see an example that works as expected
This is the link: stackblitz.com/edit/angular-ivy-hx...
As you will see, i don't duplicate the my inputs and @ViewChild work as expected :)
Enjoy the link :)
Did you try to bind structural directive as hostDirective? I still get an error TemplateRef not found -> stackoverflow.com/questions/752600...
straight forward