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 (0)