DEV Community

Gaëtan Redin
Gaëtan Redin

Posted on • Originally published at Medium on

3 1

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.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay