Directives
Types of Directives
Section titled “Types of Directives”- Structural: Modify DOM structure (
*ngIf,*ngFor,*ngSwitch) - Attribute: Modify element appearance/behavior (
ngClass,ngStyle) - Custom: Create your own reusable directives
*ngIf - Conditional Rendering
Section titled “*ngIf - Conditional Rendering”Basic usage:
import { Component } from '@angular/core';import { CommonModule } from '@angular/common';
@Component({ selector: 'app-player-status', standalone: true, imports: [CommonModule], template: ` <div *ngIf="isPlaying"> <p>🎵 Now Playing...</p> </div>
<button (click)="isPlaying = !isPlaying">Toggle Playback</button> `})export class PlayerStatusComponent { isPlaying = false;}With else:
<div *ngIf="album; else loading"> <h2>{{ album.name }}</h2> <p>{{ album.artist }}</p></div>
<ng-template #loading> <p>Loading album data...</p></ng-template>With then/else:
<div *ngIf="isPremium; then premiumContent else freeContent"></div>
<ng-template #premiumContent> <p>🎧 Premium Subscription - Unlimited Downloads</p></ng-template>
<ng-template #freeContent> <p>🎵 Free Tier - Stream Only</p></ng-template>Store result (as):
<div *ngIf="album$ | async as album"> <p>{{ album.name }}</p> <p>{{ album.artist }}</p></div>*ngFor - List Rendering
Section titled “*ngFor - List Rendering”Basic usage:
@Component({ selector: 'app-track-list', standalone: true, imports: [CommonModule], template: ` <ul> <li *ngFor="let track of tracks"> {{ track.title }} </li> </ul> `})export class TrackListComponent { tracks = [ { id: 1, title: 'Bohemian Rhapsody' }, { id: 2, title: 'Stairway to Heaven' }, { id: 3, title: 'Hotel California' } ];}With index:
<li *ngFor="let track of tracks; let i = index"> {{ i + 1 }}. {{ track.title }}</li>With trackBy for performance:
@Component({ selector: 'app-album-list', standalone: true, imports: [CommonModule], template: ` <div *ngFor="let album of albums; trackBy: trackByAlbumId"> {{ album.name }} </div> `})export class AlbumListComponent { albums = [ { id: 1, name: 'Dark Side of the Moon', artist: 'Pink Floyd' }, { id: 2, name: 'Abbey Road', artist: 'The Beatles' } ];
// Tracks items by ID, prevents unnecessary re-renders trackByAlbumId(index: number, album: any): number { return album.id; }}*All ngFor variables:
<li *ngFor="let track of tracks; let i = index; let first = first; let last = last; let even = even; let odd = odd"> Track {{ i }}: {{ track }} <span *ngIf="first">(Opening Track)</span> <span *ngIf="last">(Closing Track)</span></li>*ngSwitch - Multiple Conditions
Section titled “*ngSwitch - Multiple Conditions”@Component({ selector: 'app-player-status', standalone: true, imports: [CommonModule], template: ` <div [ngSwitch]="status"> <p *ngSwitchCase="'playing'" class="text-green">▶️ Playing</p> <p *ngSwitchCase="'paused'" class="text-yellow">⏸️ Paused</p> <p *ngSwitchCase="'stopped'" class="text-gray">⏹️ Stopped</p> <p *ngSwitchDefault class="text-red">Unknown Status</p> </div>
<button (click)="changeStatus()">Change Player Status</button> `})export class PlayerStatusComponent { status: string = 'playing';
changeStatus() { const statuses = ['playing', 'paused', 'stopped', 'unknown']; const current = statuses.indexOf(this.status); this.status = statuses[(current + 1) % statuses.length]; }}ngClass - Dynamic CSS Classes
Section titled “ngClass - Dynamic CSS Classes”Object syntax:
@Component({ selector: 'app-play-button', standalone: true, imports: [CommonModule], template: ` <button [ngClass]="{ 'btn-playing': isPlaying, 'btn-large': isLarge, 'btn-disabled': isDisabled }"> ▶️ Play Track </button> `, styles: [` .btn-playing { background: green; color: white; } .btn-large { font-size: 18px; padding: 12px; } .btn-disabled { opacity: 0.5; cursor: not-allowed; } `]})export class PlayButtonComponent { isPlaying = true; isLarge = false; isDisabled = false;}Array syntax:
<div [ngClass]="['class1', 'class2', 'class3']"> Content</div>String syntax:
<div [ngClass]="'class1 class2'"> Content</div>Method syntax:
@Component({ template: ` <div [ngClass]="getClasses()">Content</div> `})export class MyComponent { getClasses() { return { 'active': this.isActive, 'highlighted': this.isHighlighted }; }}ngStyle - Dynamic Inline Styles
Section titled “ngStyle - Dynamic Inline Styles”Object syntax:
@Component({ selector: 'app-track-title', standalone: true, imports: [CommonModule], template: ` <p [ngStyle]="{ 'color': textColor, 'font-size': fontSize + 'px', 'font-weight': isBold ? 'bold' : 'normal', 'background-color': bgColor }"> 🎵 Now Playing: Bohemian Rhapsody </p>
<input [(ngModel)]="textColor" placeholder="Color"> <input type="number" [(ngModel)]="fontSize"> `})export class TrackTitleComponent { textColor = 'blue'; fontSize = 16; isBold = false; bgColor = '#f0f0f0';}Custom Attribute Directive
Section titled “Custom Attribute Directive”Modern signal-based directive:
import { Directive, ElementRef, input, effect, inject } from '@angular/core';
@Directive({ selector: '[appHighlight]', standalone: true, host: { '(mouseenter)': 'onMouseEnter()', '(mouseleave)': 'onMouseLeave()' }})export class HighlightDirective { private el = inject(ElementRef);
// Signal-based inputs appHighlight = input<string>('yellow'); defaultColor = input<string>('transparent');
constructor() { // Set default color on init effect(() => { this.el.nativeElement.style.backgroundColor = this.defaultColor(); }); }
onMouseEnter() { this.highlight(this.appHighlight()); }
onMouseLeave() { this.highlight(this.defaultColor()); }
private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }}Usage in component:
@Component({ selector: 'app-playlist', standalone: true, imports: [HighlightDirective], template: ` <p appHighlight>🎵 Track 1: Bohemian Rhapsody</p> <p [appHighlight]="'lightblue'">🎵 Track 2: Stairway to Heaven</p> <p [appHighlight]="'pink'" [defaultColor]="'lightgray'"> 🎵 Track 3: Hotel California </p> `})export class PlaylistComponent {}Custom Structural Directive
Section titled “Custom Structural Directive”*Unless directive (opposite of ngIf):
import { Directive, input, effect, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[appUnless]', standalone: true})export class UnlessDirective { // Signal-based input appUnless = input<boolean>(false);
constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef ) { // Effect reacts to condition changes effect(() => { const condition = this.appUnless();
if (!condition) { // Show content when condition is FALSE if (this.viewContainer.length === 0) { this.viewContainer.createEmbeddedView(this.templateRef); } } else { // Remove content when condition is TRUE this.viewContainer.clear(); } }); }}Usage:
@Component({ selector: 'app-track-display', standalone: true, imports: [UnlessDirective], template: ` <p *appUnless="isHidden"> 🎵 Now Playing: Bohemian Rhapsody - Queen </p> <button (click)="isHidden = !isHidden">Toggle Track Display</button> `})export class TrackDisplayComponent { isHidden = false;}Practical Custom Directives
Section titled “Practical Custom Directives”Click Outside Directive
Section titled “Click Outside Directive”import { Directive, ElementRef, output, inject } from '@angular/core';
@Directive({ selector: '[appClickOutside]', standalone: true, host: { '(document:click)': 'onClick($event.target)' }})export class ClickOutsideDirective { private elementRef = inject(ElementRef); // Signal-based output clickOutside = output<void>();
onClick(target: HTMLElement) { const clickedInside = this.elementRef.nativeElement.contains(target); if (!clickedInside) { this.clickOutside.emit(); } }}Usage:
@Component({ template: ` <div class="genre-filter" appClickOutside (clickOutside)="closeFilter()"> <button (click)="isOpen = !isOpen">🎸 Genre Filter</button> <div *ngIf="isOpen" class="menu"> <p>🎸 Rock</p> <p>🎹 Jazz</p> <p>🎤 Pop</p> </div> </div> `})export class GenreFilterComponent { isOpen = false;
closeFilter() { this.isOpen = false; }}Auto-focus Directive
Section titled “Auto-focus Directive”import { Directive, ElementRef, OnInit, inject } from '@angular/core';
@Directive({ selector: '[appAutoFocus]', standalone: true})export class AutoFocusDirective implements OnInit { private el = inject(ElementRef);
ngOnInit() { setTimeout(() => { this.el.nativeElement.focus(); }, 0); }}Usage:
<input appAutoFocus placeholder="Search for tracks...">Debounce Click Directive
Section titled “Debounce Click Directive”import { Directive, input, output } from '@angular/core';
@Directive({ selector: '[appDebounceClick]', standalone: true, host: { '(click)': 'onClick($event)' }})export class DebounceClickDirective { // Signal-based inputs/outputs debounceTime = input<number>(500); debounceClick = output<Event>();
private timeout: any;
onClick(event: Event) { event.preventDefault(); event.stopPropagation();
clearTimeout(this.timeout); this.timeout = setTimeout(() => { this.debounceClick.emit(event); }, this.debounceTime()); }}Usage:
<button appDebounceClick [debounceTime]="1000" (debounceClick)="addToFavorites()"> ⭐ Add to Favorites (debounced)</button>Best Practices
Section titled “Best Practices”- Use structural directives for DOM manipulation
- Use attribute directives for styling/behavior
- Prefer built-in directives over custom when possible
- Use trackBy with *ngFor for performance
- Keep custom directives focused (single responsibility)
- Use HostListener for event handling
- Use HostBinding for property binding
- Make custom directives reusable and configurable
- Use standalone: true for all new directives

