HostAttributeToken - Injection token of static host attribute in Angular

Connie Leung - Apr 1 - - Dev Community

Introduction

HostAttributeToken is a new feature in Angular 17.3.0 that injects static attributes of the host node. The decorator version of HostAttributeToken is @Attribute, and it is recommended to use it over @Input because Input triggers change detection even when the input value is static.

Before Angular 17.3.0,

<app-comp  hello='A' />

@Component({
   selector: 'app-comp,
   template: `<div>{{hello}}</div>`
})
export class SomeComponent {
    hello: string;

    constructor(@Attribute('hello) hello: string) {
        this.hello = hello;   // A
    }
} 
Enter fullscreen mode Exit fullscreen mode

HostAttributeToken is an injection token that injects static attributes to follow the wave of signal evolution in Angular 17.3.0.

In this post, I will provide four examples that illustrate the usage of the HostAttributeToken.

  • Inject static attributes at the component level
  • In a directive, inject static attributes to update CSS styles
  • In a directive, inject static attributes to toggle CSS classes
  • Create a composite directive where the host directive can inject the static attributes of the host

Use case 1: Inject HostAttributeToken in a component

// host-attr-token.util.ts

import { HostAttributeToken, assertInInjectionContext, inject } from "@angular/core";

export function injectHostAttrToken<T = string>(key: string, defaultValue: T) {
  assertInInjectionContext(injectHostAttrToken);

  const token = new HostAttributeToken(key);
  return (inject(token, { optional: true }) || defaultValue) as T;    
}
Enter fullscreen mode Exit fullscreen mode

I created an injectHostAttrToken utility function that accepts a key and a default value to inject the static attribute on the host node. The function will return the default value if the attribute does not exist on the host node.

// host-attr-styles.component.ts

import { NgStyle } from "@angular/common";
import { ChangeDetectionStrategy, Component, computed } from "@angular/core";
import { injectHostAttrToken } from "../host-attr-token.util";

@Component({
  selector: 'app-host-attr',
  imports: [NgStyle],
  standalone: true,
  template: `
    <p>Inject Host Attribute Token in a component</p>
    <p [ngStyle]="styles()">
      Style this element
    </p>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HostAttrStylesComponent {
  padding = injectHostAttrToken('padding', '0.5rem');  
  fontSize = injectHostAttrToken('font-size', '18');  
  color = injectHostAttrToken('color', 'yellow');
  background = injectHostAttrToken('backgroundColor', '#abc');

  styles = computed(() => ({
    padding: this.padding,
    'fontSize.px': this.fontSize,
    color: this.color,
    background: this.background,
  }));
}
Enter fullscreen mode Exit fullscreen mode

HostAttrStylesComponent uses injectHostAttrToken to inject padding, font size, color, and background. Then, I declared a computed signal, styles, to create an object of CSS styles. Next, I assigned style() to the paragraph element's ngStyle attribute directive.

// main.ts

<app-host-attr padding='1rem' font-size='28' color='orange' backgroundColor='rebeccapurple' />
Enter fullscreen mode Exit fullscreen mode

The component injects the static attribute values and updates the CSS of padding, font size, color, and background color.

// main.ts

<app-host-attr />
Enter fullscreen mode Exit fullscreen mode

The component does not provide any attribute, and default values are used. The padding, font size, color, and background color are 0.5rem, 18px, yellow, and #abc, respectively.

Use case 2: Inject HostAttributeToken to update styles in a directive

// host-attr-styles.attr.ts

import { Directive } from "@angular/core";
import { injectHostAttrToken } from "../host-attr-token.util";

@Directive({
  selector: '[app-host-attr-styles]',
  standalone: true,
  host: {
    "[style.background]": 'background',
    "[style.padding]": 'padding',
    "[style.fontSize.px]": 'fontSize',
    "[style.color]": 'color',
  }
})
export class HostAttrStylesDirective {
  padding = injectHostAttrToken('padding', '0.5rem');  
  fontSize = injectHostAttrToken('font-size', '18');  
  color = injectHostAttrToken('color', 'yellow');
  background = injectHostAttrToken('backgroundColor', '#abc');
}
Enter fullscreen mode Exit fullscreen mode

I can also inject static attributes in a directive and apply the directive to HTML elements.

// main.ts

<p app-host-attr-styles padding='1.25rem' font-size='20' 
      color='yellow' backgroundColor='red'>
      Style by Host Attr Styles Directive
</p>
Enter fullscreen mode Exit fullscreen mode

The directive injects the static attributes and styles the paragraph element. The padding, font size, color, and background color are 1.25rem, 20px, yellow, and red, respectively.

// main.ts

<p app-host-attr-styles>Style by Host Attr Styles Directive</p>
Enter fullscreen mode Exit fullscreen mode

The paragraph element provides no attribute, and default values are used. The padding, font size, color, and background color are 0.5rem, 18px, yellow, and #abc, respectively.

Use case 3: Inject HostAttributeToken to update classes in a directive

// global_styles.css

.primary {
  background-color: #a5c2e0;
  color: #6c3183;
  font-size: 1.5rem;
  padding: 1rem;
}

.secondary {
  background-color: #69d897; 
  color: rgb(91, 89, 209);
  font-size: 1.5rem;
  padding: 0.75rem;
}
Enter fullscreen mode Exit fullscreen mode

In this use case, I defined two global styles to apply to the HTML elements in the AppComponent.

// host-attr-class.directive.ts

import { Directive } from '@angular/core';
import { injectHostAttrToken } from '../host-attr-token.util';

@Directive({
  selector: '[app-host-attr-class]',
  standalone: true,
  host: {
    '[class.primary]': 'isPrimary',
    '[class.secondary]': 'isSecondary',
  },
})
export class HostAttrClassDirective {
  type = injectHostAttrToken('type', 'primary');
  isPrimary = this.type === 'primary';
  isSecondary = this.type === 'secondary';
}
Enter fullscreen mode Exit fullscreen mode

HostAttrClassDirective injects the type static attribute, and the default value is primary.

<p app-host-attr-class type='primary'>Primary class</p>
Enter fullscreen mode Exit fullscreen mode

When type is primary, the paragraph element uses the primary class for styling.

<p app-host-attr-class type='secondary'>Secondary class</p>
Enter fullscreen mode Exit fullscreen mode

When type is secondary, the element uses the secondary class for styling.

<p app-host-attr-class>Default primary class</p>
Enter fullscreen mode Exit fullscreen mode

The paragraph element does not have the type attribute; therefore, it uses the primary class for styling.

Use case 3: Inject HostAttributeToken in a composite directive

// host-attr-composition.directive.ts

import { Directive } from "@angular/core";
import { injectHostAttrToken } from "../host-attr-token.util";
import { HostAttrStylesDirective } from "../host-attr-styles-directive/host-attr-styles.directive";

@Directive({
  selector: '[app-host-attr-composition]',
  standalone: true,
  host: {
    "[style.fontWeight]": 'fontWeight',
  },
  hostDirectives: [
    {
      directive: HostAttrStylesDirective
    }
  ]
})
export class HostAttrCompositionDirective {
  fontWeight = injectHostAttrToken('font-weight', 'bold'); 
}
Enter fullscreen mode Exit fullscreen mode

HostAttrCompositionDirective is a composite directive that registers the host directive, HostAttrStylesDirective. This directive leverages the host directive to inject the static attribute values of padding, font size, color, and background color. Moreover, it also injects the static attribute value of font weight with a default value of bold.

// main.ts 

@Component({ 
    ... other properties...
    imports: [HostAttrCompositionDirective]
})

<p app-host-attr-composition padding="1.75rem" 
      font-weight='600' font-size="32" color="rebeccapurple" 
      backgroundColor='yellow'>
      Host Attribute Composition Directive API
 </p>
Enter fullscreen mode Exit fullscreen mode

The directive updates the CSS styles of the paragraph element. Now, the element has 1.75rem padding, a font weight of 600, a font size of 32, purple text, and a yellow background.

<p app-host-attr-composition >
    Default Host Attribute Composition Directive API
</p>
Enter fullscreen mode Exit fullscreen mode

In this example, the directive styles the paragraph element with default values. The padding, font weight, font size, text, and background color are 0.5rem, bold, 18px, rebeccapurple, and yellow, respectively.

Rules of thumb

When the value is dynamic, use @Input or signal input. Otherwise, the compiler issues an error.

When the value is static, use @Attribute or HostAttributeToken. If the attribute name does not exist, @Attribute will return null. However, HostAttributeToken returns an error when the host does not provide the attribute name. Passing { option: true } to the inject function and a default value is good practice.

The following Stackblitz repo displays the final results:

I hope you like the content and continue to follow my learning experience in Angular, NestJS, and other technologies.

Resources:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .