HostAttributeToken – Injection token of static host attribute in Angular

Rmag Breaking News

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,

<appcomp hello=A />

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

constructor(@Attribute(hello) hello: string) {
this.hello = hello; // A
}
}

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;
}

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,
}));
}

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

<apphostattr padding=1rem fontsize=28 color=orange backgroundColor=rebeccapurple />

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

// main.ts

<apphostattr />

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);
}

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

// main.ts

<p apphostattrstyles padding=1.25rem fontsize=20
color=yellow backgroundColor=red>
Style by Host Attr Styles Directive
</p>

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 apphostattrstyles>Style by Host Attr Styles Directive</p>

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;
}

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;
}

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

<p app-host-attr-class type=‘primary’>Primary class</p>

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

<p app-host-attr-class type=‘secondary’>Secondary class</p>

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

<p app-host-attr-class>Default primary class</p>

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);
}

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 apphostattrcomposition padding=1.75rem
fontweight=600 fontsize=32 color=rebeccapurple
backgroundColor=yellow>
Host Attribute Composition Directive API
</p>

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>

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:

Stackblitz Demo: https://stackblitz.com/edit/stackblitz-starters-dghpis?file=src%2Fmain.ts

HostAttributeToken: https://angular.io/api/core/HostAttributeToken

Leave a Reply

Your email address will not be published. Required fields are marked *