Hey devs! ๐
During a recent discussion with some colleagues about TypeScript decorators, we ended up deep-diving into Angular.dev and stumbled upon something interesting โ the @Attribute
decorator.
Though it's not as commonly used as @Input
, it's a powerful and underutilized feature in Angular. So, I wanted to take a moment to share what I learned about it, along with a real-world example. ๐
๐ค What is @Attribute
in Angular?
The @Attribute
decorator is a parameter decorator used in a directive's constructor to inject a host element's attribute value as a static string.
It allows a class (typically a directive or component) to access a static HTML attribute value at construction time, even if itโs not bound to an Angular input.
TL;DR:
- โ Great for static values from HTML attributes
- ๐ซ Not reactive (doesnโt update on change)
- โ
Accessed during instantiation, unlike
@Input
๐ก Real-World Example: FontType Directive
Letโs look at a custom directive that changes text color based on a font
attribute using @Attribute
.
font-type.directive.ts
import {
Attribute,
Directive,
ElementRef,
inject,
OnInit,
Renderer2,
} from "@angular/core";
@Directive({
selector: "[fontType]",
})
export class FontType implements OnInit {
private render: Renderer2 = inject(Renderer2);
private el: ElementRef = inject(ElementRef);
constructor(
@Attribute("font") public font: "underline" | "italic" | "bold" | null
) {
console.log("Constructor Font Value:", font);
}
ngOnInit(): void {
switch (this.font) {
case "italic":
this.render.setStyle(this.el.nativeElement, "color", "red");
break;
case "bold":
this.render.setStyle(this.el.nativeElement, "color", "green");
break;
case "underline":
this.render.setStyle(this.el.nativeElement, "color", "orange");
break;
default:
console.warn("Font attribute not provided.");
}
}
}
๐งช Using It in a Component
@Component({
selector: "app-attribute",
imports: [FontType],
template: `
<h1><u>About Me</u></h1>
<p>
Iโm a <b>frontend developer</b> and
<b fontType font="bold">freelancer</b> passionate about building
beautiful, responsive, and user-friendly web interfaces.
</p>
<p>
I specialize in <b><u fontType font="underline">HTML</u></b
>, <b><u fontType font="underline">CSS</u></b
>, <b><u fontType font="underline">JavaScript</u></b
>, and modern frameworks like <b>React</b> and <b>Vue</b>.
</p>
<p>
<i fontType font="italic"
>I believe that great design is just as important as great code</i
>, and Iโm always exploring new technologies to enhance user experience.
</p>
<p>
Whether itโs a fresh build or revamping a legacy project,
<b
><i><u>letโs create something amazing together!</u></i></b
>
</p>
`,
styleUrls: ["./attribute.scss"],
})
export class AboutComponent {
constructor() {
console.log("About component initialized");
}
}
๐๏ธ Here, the directive is applied on <b>
, <u>
, and <i>
tags, and the color is determined by the font
attribute passed in HTML.
Output :
โ๏ธ @Input()
vs @Attribute()
While both seem similar at first glance, there are key differences:
Feature | @Input() |
@Attribute() |
---|---|---|
When it's read | After component init | During constructor execution |
Reactive | โ Yes | โ No |
Source | Angular bindings | Static HTML attribute |
Best used for | Dynamic values | Static metadata |
So, use @Attribute()
when you want a one-time read of a static attribute, and @Input()
when you want reactivity.
๐ง Under the Hood: Ivy Compilation
Angular compilers process the directive into ivy command as follow :
var FontType = class _FontType {
font;
render = inject3(Renderer2);
el = inject3(ElementRef);
constructor(font) {
this.font = font;
console.log(font);
}
ngOnInit() {
switch (this.font) {
case "italic":
{
this.render.setStyle(this.el.nativeElement, "color", "red");
break;
}
;
case "bold":
{
this.render.setStyle(this.el.nativeElement, "color", "green");
break;
}
;
case "underline":
{
this.render.setStyle(this.el.nativeElement, "color", "orange");
break;
}
;
default:
{
console.log("font not provided");
}
}
}
static \u0275fac = function FontType_Factory(__ngFactoryType__) {
return new (__ngFactoryType__ || _FontType)(i04.\u0275\u0275injectAttribute("font"));
}
;
static \u0275dir = /* @__PURE__ */
i04.\u0275\u0275defineDirective({
type: _FontType,
selectors: [["", "fontType", ""]]
});
}
;
( () => {
(typeof ngDevMode === "undefined" || ngDevMode) && i04.\u0275setClassMetadata(FontType, [{
type: Directive,
args: [{
selector: "[fontType]"
}]
}], () => [{
type: void 0,
decorators: [{
type: Attribute,
args: ["font"]
}]
}], null);
}
)();
Hereโs a quick peek at what Angular does behind the scenes when using @Attribute
:
static ษตfac = function FontType_Factory(t) {
return new (t || FontType)(ษตษตinjectAttribute('font'));
}
This shows that Angular injects the value during directive instantiation.
๐ซ When Not to Use @Attribute
- If the attribute is bound dynamically via Angular (
[attr.font]
) โ use@Input()
instead. - If you need to track changes to the attribute over time.
๐งช Bonus Test: Changing Attribute Dynamically
Letโs see what happens when we try to change the attribute after the view is initialized:
ngAfterViewInit(): void {
console.log('Before changing font attribute:', this.font);
this.render.setAttribute(this.el.nativeElement, 'font', 'some_random_value');
console.warn('After changing font attribute:', this.font);
}
๐ Result: The font
value remains the same because @Attribute()
only reads the value once during construction โ no updates afterward!
โ Wrap Up
The @Attribute
decorator is perfect for grabbing static, non-dynamic metadata from the DOM at construction time. While it's not as flexible as @Input
, it serves a specific purpose in Angular's ecosystem.
So next time youโre building a directive or component and just need a static HTML attribute โ give @Attribute()
a try! ๐ฏ
๐ฌ Have you used @Attribute
in your projects? Got a cool use case or questions? Letโs chat in the comments below!
โ๏ธ Author: Vetriselvan
๐จโ๐ป Frontend Developer | Code Lover | Exploring Angularโs future
Top comments (0)