Angular Directives Explained — Structural and Attribute Directives Guide
In this tutorial, you will learn about Angular Directives Explained. We cover key concepts, practical examples, and best practices to help you master this topic.
Angular directives are classes that add behavior to elements in your templates, either by manipulating the DOM structure or by changing the appearance and behavior of existing elements.
What You'll Learn
- The difference between structural and attribute directives
- How to use
*ngIf,*ngFor, and*ngSwitch - How to use
ngClass,ngStyle, andngModel - How to build custom directives
- When to use directives versus components
Why It Matters
Directives let you write less code by reusing DOM manipulation logic across your app. Instead of writing the same conditional rendering code in every component, you apply a directive. This keeps templates clean and logic centralized.
Real-World Use
Durga Antivirus Pro uses an appHighlight directive to color-code threat levels. Structural directives control whether the scan results panel, empty state, or loading spinner appears based on the current scan status.
flowchart TD
A[Directives] --> B[Structural]
A --> C[Attribute]
B --> D[*ngIf]
B --> E[*ngFor]
B --> F[*ngSwitch]
C --> G[ngClass]
C --> H[ngStyle]
C --> I[Custom Directives]
style A fill:#f97316,color:#fff
Structural Directives
Structural directives change the DOM layout by adding, removing, or replacing elements. They start with an asterisk * as syntactic sugar.
*ngIf
*ngIf conditionally includes or excludes an element from the DOM:
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
@Component({
selector: "app-login-status",
standalone: true,
imports: [CommonModule],
template: `
<div *ngIf="isLoggedIn; else loginPrompt">
Welcome back, {{ username }}!
<button (click)="logout()">Logout</button>
</div>
<ng-template #loginPrompt>
<p>Please log in to continue.</p>
<button (click)="login()">Login</button>
</ng-template>
`
})
export class LoginStatusComponent {
isLoggedIn = false;
username = "guest";
login() { this.isLoggedIn = true; }
logout() { this.isLoggedIn = false; }
}
Expected output: When isLoggedIn is false, the "Please log in" message appears. After clicking Login, the welcome message replaces it.
The else syntax references an ng-template element by its template reference variable. When the condition is false, Angular removes the element with *ngIf and renders the ng-template instead.
*ngFor
*ngFor iterates over a collection and renders a template for each item:
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
@Component({
selector: "app-task-list",
standalone: true,
imports: [CommonModule],
template: `
<ul>
<li *ngFor="let task of tasks; let i = index; trackBy: trackById">
<span>{{ i + 1 }}.</span>
<span [style.textDecoration]="task.done ? 'line-through' : 'none'">
{{ task.title }}
</span>
<button (click)="toggleTask(i)">{{ task.done ? "Undo" : "Done" }}</button>
</li>
</ul>
`
})
export class TaskListComponent {
tasks = [
{ id: 1, title: "Learn Angular", done: false },
{ id: 2, title: "Build an app", done: false },
{ id: 3, title: "Deploy to production", done: false },
];
toggleTask(index: number) {
this.tasks[index].done = !this.tasks[index].done;
}
trackById(_index: number, task: { id: number }) {
return task.id;
}
}
Expected output: A numbered task list with toggle buttons. Completed tasks show with strikethrough text.
The trackBy function helps Angular identify which items changed. Without it, Angular re-renders the entire list whenever the array reference changes. With trackBy, it only updates the changed items.
*ngSwitch
*ngSwitch displays one template from a set of choices:
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
@Component({
selector: "app-status-message",
standalone: true,
imports: [CommonModule],
template: `
<div [ngSwitch]="status">
<p *ngSwitchCase="'loading'">Fetching data, please wait...</p>
<p *ngSwitchCase="'success'">Data loaded successfully!</p>
<p *ngSwitchCase="'error'">An error occurred. Please try again.</p>
<p *ngSwitchDefault>Unknown status.</p>
</div>
<button (click)="cycleStatus()">Next Status</button>
`
})
export class StatusMessageComponent {
status: "loading" | "success" | "error" = "loading";
private states = ["loading", "success", "error"] as const;
private index = 0;
cycleStatus() {
this.index = (this.index + 1) % this.states.length;
this.status = this.states[this.index];
}
}
Expected output: A message that cycles through loading, success, and error states with each button click.
ngSwitchCase defines each possible value. ngSwitchDefault is the fallback when no case matches. Angular removes all elements that do not match the current value.
Attribute Directives
Attribute directives change the appearance or behavior of an element without altering its structure.
ngClass
ngClass dynamically adds or removes CSS classes:
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
@Component({
selector: "app-priority-label",
standalone: true,
imports: [CommonModule],
template: `
<span [ngClass]="{
'priority-high': priority === 'high',
'priority-medium': priority === 'medium',
'priority-low': priority === 'low'
}">
{{ priority | uppercase }}
</span>
<button (click)="cyclePriority()">Change Priority</button>
`,
styles: [`
.priority-high { color: white; background: #ef4444; padding: 4px 8px; border-radius: 4px; }
.priority-medium { color: white; background: #f97316; padding: 4px 8px; border-radius: 4px; }
.priority-low { color: white; background: #22c55e; padding: 4px 8px; border-radius: 4px; }
`]
})
export class PriorityLabelComponent {
priority: "high" | "medium" | "low" = "medium";
private priorities = ["high", "medium", "low"] as const;
private index = 1;
cyclePriority() {
this.index = (this.index + 1) % this.priorities.length;
this.priority = this.priorities[this.index];
}
}
Expected output: A colored priority label that cycles through red (high), orange (medium), and green (low) on button click.
The object passed to ngClass maps class names to boolean conditions. When a condition is true, Angular adds that class to the element. When false, it removes it.
ngStyle
ngStyle sets inline styles dynamically:
@Component({
selector: "app-color-picker",
standalone: true,
template: `
<div [ngStyle]="{
'background-color': bgColor,
'color': textColor,
'font-size.px': fontSize,
'padding.px': 16,
'border-radius.px': 8
}">
Styled dynamically with ngStyle
</div>
<label>Background: <input [(ngModel)]="bgColor" /></label>
`,
imports: [FormsModule]
})
export class ColorPickerComponent {
bgColor = "#f97316";
textColor = "white";
fontSize = 18;
}
Expected output: A colored box whose background, text color, and font size change based on input values.
ngStyle accepts an object where keys are CSS property names and values are the property values. Suffix keys like font-size.px specify units.
Custom Directives
You can create your own directives using the @Directive decorator:
import { Directive, ElementRef, HostListener, Input } from "@angular/core";
@Directive({
selector: "[appHighlight]",
standalone: true
})
export class HighlightDirective {
@Input() appHighlight = "";
@Input() highlightDuration = 2000;
constructor(private el: ElementRef) {}
@HostListener("mouseenter") onMouseEnter() {
this.el.nativeElement.style.backgroundColor = this.appHighlight || "yellow";
}
@HostListener("mouseleave") onMouseLeave() {
this.el.nativeElement.style.backgroundColor = "transparent";
}
}
Usage:
<p [appHighlight]="'lightblue'" highlightDuration="1000">Hover over me!</p>
Expected output: When you hover over the paragraph, its background changes to light blue. When you leave, it returns to transparent.
The @Directive selector uses brackets to indicate an attribute selector. @HostListener registers event handlers on the host element. ElementRef gives access to the native DOM element.
Common Mistakes
Using multiple structural directives on one element — Angular does not allow
*ngIfand*ngForon the same element. Wrap one in an<ng-container>instead.Forgetting the asterisk prefix — Writing
ngIf="condition"instead of*ngIf="condition"silently fails because Angular expects the structural directive syntax.Mutating arrays in ngFor — Pushing to or splicing an array does not trigger change detection if the array reference stays the same. Reassign the array or use immutability.
Using ngClass with conflicting classes — If two conditions in the
ngClassobject are true for the same class, the later one in the object wins, which can be confusing.Not cleaning up in custom directives — If your directive subscribes to events or observables, unsubscribe in
ngOnDestroyto prevent memory leaks.
Practice Questions
What is the difference between structural and attribute directives? Structural directives change the DOM layout (add/remove elements). Attribute directives change appearance or behavior of existing elements.
Why does Angular not allow multiple structural directives on one element? Angular's microsyntax would create ambiguous precedence. Use
<ng-container>to wrap one directive.What does
trackBydo in*ngFor? It provides a unique identifier for each item so Angular can track changes efficiently without re-rendering the entire list.How do you create a custom attribute directive? Use the
@Directivedecorator with a selector, injectElementRef, and use@HostListenerfor events.What is the
ngSwitchDefaultfor? It renders when nongSwitchCasematches the switch expression.
Challenge
Create a custom appTooltip directive that shows a tooltip on hover. Use @Input() for the tooltip text and position (top, bottom, left, right). Style it with absolute positioning relative to the host element.
FAQ
Mini Project
Build a PermissionGuardDirective that hides elements based on user roles. Create a custom structural directive *appHasRole that takes a role string and removes the element from the DOM if the current user does not have that role. Combine it with a login component that cycles through admin, editor, and viewer roles.
What's Next
Continue learning about data transformation and services:
Angular Pipes, Angular Services, Angular Components
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro