Suggest an editImprove this articleRefine the answer for “What are directives and what types exist in Angular?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Angular directive** is a class with `@Directive` that extends HTML element behavior. Three types exist: attribute directives change appearance or behavior (`ngClass`, `ngStyle`), structural directives add or remove DOM elements (`*ngIf`, `*ngFor`), and component directives combine both with their own template. ```typescript @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { @HostListener('mouseenter') onHover() { /* change style */ } } ``` **Key rule:** need DOM logic without UI? Directive. Need UI? Component.Shown above the full answer for quick recall.Answer (EN)Image**Angular directive** is a class decorated with `@Directive` that Angular uses to extend or transform HTML elements during template compilation. ## Theory ### TL;DR - Directives are like HTML plugins: attach one to any element to add styles, events, or DOM changes without touching the element itself - 3 types: **Attribute** (decorates existing elements), **Structural** (adds or removes DOM nodes), **Component** (directive with its own template) - Attribute selector: `[appHighlight]`. Structural uses the `*` prefix: `*ngIf`. Component uses a tag: `<app-card>` - Need reusable DOM behavior? Directive. Need a UI block with markup? Component. ### Quick Example The classic `appHighlight` attribute directive shows the full pattern in about 12 lines: ```html <div appHighlight>Hover me</div> ``` ```typescript import { Directive, ElementRef, HostListener } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor(private el: ElementRef) {} @HostListener('mouseenter') onEnter() { this.el.nativeElement.style.backgroundColor = 'yellow'; } @HostListener('mouseleave') onLeave() { this.el.nativeElement.style.backgroundColor = null; } } ``` Angular matches the `[appHighlight]` attribute to the selector, injects `ElementRef`, and `@HostListener` wires the mouse events. No template needed. ### Key Difference Attribute directives decorate an existing element: they change its style, class, or behavior but leave the DOM structure alone. Structural directives reshape the DOM. `*ngIf` removes an element entirely when the condition is false. `*ngFor` clones a template node for each item in an array. Components are technically directives too, just with an added template and view encapsulation. That separation lets Angular skip template compilation for attribute directives entirely, while structural ones trigger `ViewContainerRef` manipulation on every change. ### When to Use - Changing styles or classes on an element: Attribute directive (`ngClass`, `ngStyle`, custom validator styling) - Conditional rendering or iteration: Structural (`*ngIf`, `*ngFor`, `*ngSwitch`) - Reusable UI with its own markup: Component, not a directive - Attaching event handlers to a host element: Attribute with `@HostListener` or `@HostBinding` - Logic with no DOM dependency at all: Service, not a directive ### Directive Types | Type | Selector syntax | DOM impact | Key Angular APIs | Built-in examples | |------|-----------------|------------|-----------------|-------------------| | **Attribute** | `[appHighlight]` | None (decorates host element) | `ElementRef`, `HostListener`, `HostBinding` | `ngClass`, `ngStyle` | | **Structural** | `*appUnless`, `*ngIf` | Adds or removes elements | `ViewContainerRef`, `TemplateRef` | `*ngIf`, `*ngFor`, `*ngSwitch` | | **Component** | `<app-user-card>` | Renders its own template | `@Component` (extends `@Directive`) | Any custom UI block | ### How Angular Processes Directives During AOT compilation Angular scans templates, matches selectors to `@Directive` metadata, and generates factory functions. At runtime the framework creates directive instances via dependency injection and runs lifecycle hooks in order: `ngOnInit`, then `ngAfterViewInit`. For structural directives, any state change calls `createEmbeddedView()` or `clear()` on `ViewContainerRef`. That is how `*ngIf` removes and re-inserts DOM nodes without a full re-render. Attribute directives skip that step completely, which is why they cost less inside large lists. ### Common Mistakes **1. Direct `nativeElement` access instead of `Renderer2`** ```typescript // Breaks SSR (Angular Universal) constructor(el: ElementRef) { el.nativeElement.style.color = 'red'; } // Correct: Renderer2 works in both browser and server contexts constructor(private el: ElementRef, private renderer: Renderer2) { renderer.setStyle(el.nativeElement, 'color', 'red'); } ``` Direct `nativeElement` manipulation causes hydration mismatches in Angular Universal. `Renderer2` abstracts the DOM so Angular handles both environments correctly. **2. Forgetting `TemplateRef` and `ViewContainerRef` in structural directives** ```typescript // The template never renders @Directive({ selector: '[appIf]' }) export class AppIfDirective { @Input() appIf: boolean; // Angular has nowhere to insert the template } // Correct export class AppIfDirective { constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef ) {} @Input() set appIf(condition: boolean) { condition ? this.viewContainer.createEmbeddedView(this.templateRef) : this.viewContainer.clear(); } } ``` This is the most common "directive not rendering" bug. The `*` syntax is syntactic sugar. Angular still needs `ViewContainerRef` to physically insert the template into the DOM. **3. Multiple structural directives on one element** ```html <!-- Angular applies only the first structural directive, the second is ignored --> <div *ngFor="let item of items" *ngIf="item.active">{{ item.name }}</div> <!-- Correct: nest them --> <div *ngFor="let item of items"> <span *ngIf="item.active">{{ item.name }}</span> </div> ``` **4. Missing `$event` in `@HostListener`** ```typescript @HostListener('click') onClick() {} // No access to coordinates or target @HostListener('click', ['$event']) onClick(event: MouseEvent) { console.log(event.target); // Works as expected } ``` ### Real-World Usage - Angular Material: `matTooltip` adds hover tips to any element via a single attribute - NG Bootstrap: `ngbTooltip` binds the same way to Bootstrap CSS classes - PrimeNG: `pTooltip` used in enterprise data table cells - Nx workspaces: custom structural `*nxLoading` for lazy module loading states - Reactive Forms: custom attribute validators integrate directly with Angular's `NgForm` control system ### Follow-up Questions **Q:** What is the difference between `@Directive` and `@Component`? **A:** `@Component` extends `@Directive` and adds a template, styles, and view encapsulation. A component is a directive that knows how to render itself. A plain `@Directive` has no template and no associated view. **Q:** How does `*ngFor` use `trackBy` and why does it matter? **A:** `trackBy` takes a function that returns a unique identifier per item. Angular reuses existing DOM nodes when the array changes instead of destroying and recreating every element. Without it, any mutation like sort, filter, or push rebuilds the entire list in the DOM. **Q:** Build a custom structural directive. What do you need? **A:** Inject `TemplateRef<any>` and `ViewContainerRef` in the constructor. Add an `@Input` setter. When the condition is true, call `this.viewContainer.createEmbeddedView(this.templateRef)`. When false, call `this.viewContainer.clear()`. Use it in templates as `*appDirectiveName="expression"`. **Q:** Why use `@HostBinding` instead of a `[style.color]` binding in the template? **A:** `@HostBinding` lives inside the directive class, works correctly in inheritance chains, and is SSR-safe. Template bindings require you to control the template, which is not always the case for a shared directive library. **Q:** What is the performance cost of directives in large lists? **A:** Structural directives recreate views on every change detection pass if the parent uses the default strategy. Attribute directives are lighter, but `@HostListener` callbacks fire on every matching event regardless. Fix: `ChangeDetectionStrategy.OnPush` on the parent and `trackBy` in every `*ngFor`. Angular 17 signals reduce unnecessary checks further by reacting only to specific state changes. ## Examples ### Basic: SSR-safe hover highlight ```html <p appHighlight>Hover to highlight</p> ``` ```typescript import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor(private el: ElementRef, private renderer: Renderer2) {} @HostListener('mouseenter') onEnter() { // Renderer2 keeps this safe for Angular Universal this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', 'yellow'); } @HostListener('mouseleave') onLeave() { this.renderer.removeStyle(this.el.nativeElement, 'backgroundColor'); } } ``` `Renderer2` instead of direct `nativeElement` access is what separates a quick prototype from a production directive. The same attribute works on any element in any template. ### Intermediate: Custom structural directive ```typescript import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[appUnless]' }) export class UnlessDirective { private hasView = false; constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef ) {} @Input() set appUnless(condition: boolean) { if (!condition && !this.hasView) { this.viewContainer.createEmbeddedView(this.templateRef); this.hasView = true; } else if (condition && this.hasView) { this.viewContainer.clear(); this.hasView = false; } } } ``` ```html <div *appUnless="isLoading">Content is ready</div> ``` The `hasView` flag prevents duplicate renders when the input changes rapidly. This is exactly the pattern behind `*ngIf`. I've seen this exercise come up in senior Angular interviews at mid-size product companies, and the `hasView` guard is usually what separates a passing answer from a strong one. ### Advanced: Structural directive with async input ```typescript import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[appUnlessLoading]' }) export class UnlessLoadingDirective { private hasView = false; @Input() set appUnlessLoading(loading: Promise<boolean> | boolean) { if (typeof loading === 'boolean') { this.updateView(loading); } else { // Async resolution: Promise resolves to final loading state loading.then(result => this.updateView(result)); } } constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef ) {} private updateView(loading: boolean) { if (!loading && !this.hasView) { this.viewContainer.createEmbeddedView(this.templateRef); this.hasView = true; } else if (loading && this.hasView) { this.viewContainer.clear(); this.hasView = false; } } } ``` ```html <div *appUnlessLoading="fetchingUsers$ | async">Users are loaded</div> ``` The real gotcha: without the `hasView` guard, rapid async changes call `createEmbeddedView` multiple times and leak duplicate DOM nodes into the view. `clear()` removes all views before creating a new one, but checking `hasView` first avoids the unnecessary DOM work entirely.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.