Angular 15: hostDirectives + InjectionToken
How to avoid duplications and override directive’s inputs value with injection token
Hey, as you know, Angular 15 has been come with a new great feature: hostDirectives.
Since Angular 15 release, I have made some refacto in my angular project and I found this use case: I extend a directive which have an input to handle css classes. In the subsclass, I override the input property and set it a default value. I wanted to remove the extends and find a way to use only the new concept of hostDirectives. And Actually, I found a way which result of the combination of hostDirectives and injection token.
Let’s go for the demo.
Here is a directive which is used to be extended to add some css classes :
@Directive({
selector: '[typography]',
standalone: true,
host: {
'[class.headline-1]': 'typography === "headline-1"',
...
'[class.headline-6]': 'typography === "headline-6"',
'[class.subtitle-1]': 'typography === "subtitle-1"',
'[class.subtitle-2]': 'typography === "subtitle-2"',
'[class.body-1]': 'typography === "body-1"',
'[class.body-2]': 'typography === "body-2"',
'[class.button-typo]': 'typography === "button"',
'[class.caption]': 'typography === "caption"',
},
})
export class TypographyDirective {
@Input('rcTypography') public typography!:
| 'headline-1'
| 'headline-2'
| 'headline-3'
| 'headline-4'
| 'headline-5'
| 'headline-6'
| 'subtitle-1'
| 'subtitle-2'
| 'body-1'
| 'body-2'
| 'button'
| 'caption';
}
And this is how it was used before:
@Component({
selector: 'my-selector',
standalone: true,
template: `...`,
})
export class SubClassComponent extends TypographyDirective {
public override readonly typography = 'body-2';
}
Why? because I wanted to use the advantage of the directive directly on my component and to not rewrite the css classes handling.
It’s ok, it works, it’s readable.
But what if I need to extend a more functionnal class? I have to use mixins ? yes ok I know it’s possible too. But it needs to write more code each time.
A simple way will be to use the combination of hostDirectives metadata and an injection token.
Let’s do it.
// the injection token
export const TYPOGRAPHY_TOKEN: InjectionToken<TypographyDirective['typography']> = new InjectionToken<
TypographyDirective['typography']
>('TYPOGRAPHY_TOKEN');
// the updated base directive
@Directive({
selector: '[typography]',
standalone: true,
host: {
'[class.headline-1]': 'typography === "headline-1"',
...
'[class.headline-6]': 'typography === "headline-6"',
'[class.subtitle-1]': 'typography === "subtitle-1"',
'[class.subtitle-2]': 'typography === "subtitle-2"',
'[class.body-1]': 'typography === "body-1"',
'[class.body-2]': 'typography === "body-2"',
'[class.button-typo]': 'typography === "button"',
'[class.caption]': 'typography === "caption"',
},
})
export class TypographyDirective {
@Input('rcTypography') public typography:
| 'headline-1'
| 'headline-2'
| 'headline-3'
| 'headline-4'
| 'headline-5'
| 'headline-6'
| 'subtitle-1'
| 'subtitle-2'
| 'body-1'
| 'body-2'
| 'button'
| 'caption'
| null = inject(TYPOGRAPHY_TOKEN, { optional: true });
}
The property will be valued by the Input value of by the token value by default. Here’s the updated subclass:
@Component({
selector: 'my-selector',
standalone: true,
template: `...`,
hostDirectives: [
{
directive: TypographyDirective,
},
],
providers: [
{
provide: TYPOGRAPHY_TOKEN,
useValue: 'body-2',
},
],
})
export class SubClassComponent {
}
Et voila! My subclass is free to extend another class, the final code is still readable, it’s perfect. Hmm, wait, what if I want to allow to override the typography property with an input ?
Okay, you just have to indicate the input property like this:
@Component({
selector: 'my-selector',
standalone: true,
template: `...`,
hostDirectives: [
{
directive: TypographyDirective,
inputs: ['typography']
},
],
providers: [
{
provide: TYPOGRAPHY_TOKEN,
useValue: 'body-2',
},
],
})
export class SubClassComponent {
}
and call it like this:
<my-selector ... [typography]="'subtitle-2'"></my-selector>
Even if the injection token is valued, the input value will have the final word.
I hope this will help someone to simplify his code et to write more readable code.
Thanks for reading.