DEV Community

Cover image for Exploring the `@Attribute` Decorator in Angular 🔍
vetriselvan Panneerselvam
vetriselvan Panneerselvam

Posted on

Exploring the `@Attribute` Decorator in Angular 🔍

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.");
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

🧪 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");
  }
}
Enter fullscreen mode Exit fullscreen mode

🖍️ Here, the directive is applied on <b>, <u>, and <i> tags, and the color is determined by the font attribute passed in HTML.

Output :

Image description

⚔️ @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);
}
)();
Enter fullscreen mode Exit fullscreen mode

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'));
}
Enter fullscreen mode Exit fullscreen mode

This shows that Angular injects the value during directive instantiation.

Image description

Image description

Image description

🚫 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);
}
Enter fullscreen mode Exit fullscreen mode

Image description

Image description

🔍 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 (2)

Collapse
 
devops_fundamental profile image
DevOps Fundamental

This was such a well-written and thoughtful post—I really enjoyed the way you explained your ideas and offered a fresh perspective. Looking forward to reading more from you!

Collapse
 
vetriselvan_11 profile image
vetriselvan Panneerselvam

Really appreciate the feedback. Always happy to learn and improve. Feel free to share more anytime