DEV Community

Gaëtan Redin
Gaëtan Redin

Posted on • Originally published at Medium on

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';
}
Enter fullscreen mode Exit fullscreen mode

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';
}
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

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 {
}
Enter fullscreen mode Exit fullscreen mode

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 {
}
Enter fullscreen mode Exit fullscreen mode

and call it like this:

<my-selector ... [typography]="'subtitle-2'"></my-selector>
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)