Streamlining Attribute Injection in Angular: The HostAttributeToken Approach

Netanel Basal
Netanel Basal
Published in
2 min readFeb 29, 2024

--

Angular offers the @Attribute decorator, facilitating the injection of attributes from the host node. It proves particularly handy when there’s no necessity to establish a binding.

However, there’s a incomplete counterpart for the inject function. Angular v17.3.0 introduces the new HostAttributeToken class, allowing for attribute injection similar to @Attribute. Here’s how you can use it:

import { Component, HostAttributeToken } from '@angular/core';

@Component({
selector: 'app-foo',
standalone: true
})
export class FooComponent {
variation = inject(new HostAttributeToken('variation'));
}

This new API behaves similarly to @Attribute, with one crucial difference: it throws a DI error when the attribute doesn’t exist, rather than returning null like @Attribute. This change aligns its behavior more closely with other injection tokens.

<app-foo variation="primary" />

It’s worth noting that attribute values must be static; dynamic bindings or interpolations won’t function here.

For optional attributes with a default value, we can pass the optional option:

import { Component, HostAttributeToken } from '@angular/core';

@Component({
selector: 'app-foo',
standalone: true
})
export class FooComponent {
variation: Variation = inject(
new HostAttributeToken('variation'), { optional: true }
) || 'primary';
}

This pattern maintains flexibility while ensuring fallback values when attributes are absent.

For further simplification of attribute handling, consider utilizing the following API:

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

export function hostAttr<R>(key: string, defaultValue: R): R {
assertInInjectionContext(hostAttr);

return (
(inject(new HostAttributeToken(key), { optional: true }) as R) ??
defaultValue
);
}

hostAttr.required = function <R>(key: string): R {
assertInInjectionContext(hostAttr);
return inject(new HostAttributeToken(key)) as R;
};

Now, utilizing attributes in your components becomes seamless and more aligned with the input and model functions:

@Component({
selector: 'app-foo',
standalone: true
})
export class FooComponent {
variation = hostAttr<Variation>('variation', 'primary');
variation = hostAttr.required<Variation>('variation');
}

We can also take it one step further and add an option to pass a parser:

@Component({
selector: 'app-foo',
standalone: true
})
export class FooComponent {
page = hostAttr.required('page', toNumber);
}

By using HostAttributeToken, you make your code compatible with elements like ng-container and ng-template, ensuring safer usage in server-side rendering (SSR) scenarios.

🙏 Support ngneat & Netanel Basal: Get Featured!

Are you passionate about the ngneat open source libraries for Angular or find Netanel Basal’s blog posts invaluable for your learning journey? Show your support by sponsoring my work!

Follow me on Medium or Twitter to read more about Angular and JS!

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.