Directives are classes that add behavior to elements in your Angular applications. Angular has three types of directives: components (directives with templates), structural directives, and attribute directives.
Types of Directives
Components Directives with templates
Structural Change DOM layout (add/remove elements)
Attribute Change appearance or behavior of elements
Built-in Structural Directives
Modern Angular uses built-in control flow (@if, @for, @switch) instead of structural directives. These are still available for compatibility.
*ngIf (Legacy)
import { Component } from '@angular/core' ;
import { NgIf } from '@angular/common' ;
@ Component ({
selector: 'app-conditional' ,
standalone: true ,
imports: [ NgIf ],
template: `
<div *ngIf="isVisible">This content is visible</div>
<div *ngIf="user; else noUser">
Welcome, {{ user.name }}!
</div>
<ng-template #noUser>
<p>Please log in.</p>
</ng-template>
<!-- With then/else -->
<div *ngIf="isLoading; then loading else content"></div>
<ng-template #loading>Loading...</ng-template>
<ng-template #content>Content loaded!</ng-template>
`
})
export class ConditionalComponent {
isVisible = true ;
user : { name : string } | null = null ;
isLoading = false ;
}
*ngFor (Legacy)
import { Component } from '@angular/core' ;
import { NgFor } from '@angular/common' ;
@ Component ({
selector: 'app-list' ,
standalone: true ,
imports: [ NgFor ],
template: `
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<!-- With index -->
<div *ngFor="let user of users; let i = index">
{{ i + 1 }}. {{ user.name }}
</div>
<!-- With trackBy -->
<div *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</div>
<!-- With context variables -->
<div *ngFor="let item of items; let first = first; let last = last; let even = even">
<span [class.first]="first" [class.last]="last" [class.even]="even">
{{ item }}
</span>
</div>
`
})
export class ListComponent {
items = [ 'Item 1' , 'Item 2' , 'Item 3' ];
users = [{ id: 1 , name: 'Alice' }, { id: 2 , name: 'Bob' }];
trackById ( index : number , item : any ) {
return item . id ;
}
}
*ngSwitch (Legacy)
import { Component } from '@angular/core' ;
import { NgSwitch , NgSwitchCase , NgSwitchDefault } from '@angular/common' ;
@ Component ({
selector: 'app-switch' ,
standalone: true ,
imports: [ NgSwitch , NgSwitchCase , NgSwitchDefault ],
template: `
<div [ngSwitch]="status">
<p *ngSwitchCase="'loading'">Loading...</p>
<p *ngSwitchCase="'success'">Success!</p>
<p *ngSwitchCase="'error'">Error occurred</p>
<p *ngSwitchDefault>Unknown status</p>
</div>
`
})
export class SwitchComponent {
status : 'loading' | 'success' | 'error' | 'unknown' = 'loading' ;
}
Built-in Attribute Directives
ngClass
Dynamically add/remove CSS classes:
import { Component } from '@angular/core' ;
import { NgClass } from '@angular/common' ;
@ Component ({
selector: 'app-styling' ,
standalone: true ,
imports: [ NgClass ],
template: `
<!-- String syntax -->
<div [ngClass]="'class1 class2'">Content</div>
<!-- Array syntax -->
<div [ngClass]="['class1', 'class2', condition ? 'class3' : '']">Content</div>
<!-- Object syntax -->
<div [ngClass]="{
'active': isActive,
'disabled': isDisabled,
'highlighted': isHighlighted
}">Content</div>
<!-- Expression -->
<div [ngClass]="getClasses()">Content</div>
` ,
styles: [ `
.active { color: green; }
.disabled { opacity: 0.5; }
.highlighted { background: yellow; }
` ]
})
export class StylingComponent {
isActive = true ;
isDisabled = false ;
isHighlighted = true ;
condition = false ;
getClasses () {
return {
'active' : this . isActive ,
'disabled' : this . isDisabled
};
}
}
ngStyle
Dynamically set inline styles:
import { Component } from '@angular/core' ;
import { NgStyle } from '@angular/common' ;
@ Component ({
selector: 'app-inline-styles' ,
standalone: true ,
imports: [ NgStyle ],
template: `
<!-- Object syntax -->
<div [ngStyle]="{
'color': textColor,
'font-size': fontSize + 'px',
'background-color': bgColor
}">Styled text</div>
<!-- Expression -->
<div [ngStyle]="getStyles()">More styled text</div>
<!-- With units -->
<div [ngStyle]="{
'width.px': width,
'height.%': height
}">Sized box</div>
`
})
export class InlineStylesComponent {
textColor = 'blue' ;
fontSize = 16 ;
bgColor = '#f0f0f0' ;
width = 200 ;
height = 50 ;
getStyles () {
return {
'color' : this . textColor ,
'font-size' : this . fontSize + 'px'
};
}
}
ngModel
Two-way data binding for form inputs:
import { Component } from '@angular/core' ;
import { FormsModule } from '@angular/forms' ;
@ Component ({
selector: 'app-forms' ,
standalone: true ,
imports: [ FormsModule ],
template: `
<input [(ngModel)]="name" placeholder="Enter name">
<p>Hello, {{ name }}!</p>
<input type="checkbox" [(ngModel)]="isChecked">
<p>Checked: {{ isChecked }}</p>
<select [(ngModel)]="selectedOption">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
<p>Selected: {{ selectedOption }}</p>
`
})
export class FormsComponent {
name = '' ;
isChecked = false ;
selectedOption = '1' ;
}
Custom Attribute Directive
Create your own attribute directive:
import { Directive , ElementRef , HostListener , Input , inject } from '@angular/core' ;
@ Directive ({
selector: '[appHighlight]' ,
standalone: true
})
export class HighlightDirective {
private el = inject ( ElementRef );
@ Input () appHighlight = '' ;
@ Input () defaultColor = 'yellow' ;
@ HostListener ( 'mouseenter' ) onMouseEnter () {
this . highlight ( this . appHighlight || this . defaultColor );
}
@ HostListener ( 'mouseleave' ) onMouseLeave () {
this . highlight ( '' );
}
private highlight ( color : string ) {
this . el . nativeElement . style . backgroundColor = color ;
}
}
// Usage
@ Component ({
selector: 'app-demo' ,
standalone: true ,
imports: [ HighlightDirective ],
template: `
<p appHighlight>Hover over me (default yellow)</p>
<p [appHighlight]="'lightblue'">Hover over me (light blue)</p>
<p appHighlight defaultColor="pink">Hover over me (pink)</p>
`
})
export class DemoComponent { }
Custom Structural Directive
Create a custom structural directive:
import { Directive , Input , TemplateRef , ViewContainerRef , inject } from '@angular/core' ;
@ Directive ({
selector: '[appUnless]' ,
standalone: true
})
export class UnlessDirective {
private templateRef = inject ( TemplateRef < any > );
private viewContainer = inject ( ViewContainerRef );
private hasView = false ;
@ 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 ;
}
}
}
// Usage
@ Component ({
selector: 'app-demo' ,
standalone: true ,
imports: [ UnlessDirective ],
template: `
<p *appUnless="isHidden">This is shown when isHidden is false</p>
<button (click)="isHidden = !isHidden">Toggle</button>
`
})
export class DemoComponent {
isHidden = false ;
}
import { Directive , ElementRef , Input , OnInit , inject } from '@angular/core' ;
@ Directive ({
selector: '[appStyler]' ,
standalone: true
})
export class StylerDirective implements OnInit {
private el = inject ( ElementRef );
@ Input () textColor = 'black' ;
@ Input () bgColor = 'white' ;
@ Input () fontSize = 16 ;
ngOnInit () {
this . applyStyles ();
}
private applyStyles () {
const element = this . el . nativeElement ;
element . style . color = this . textColor ;
element . style . backgroundColor = this . bgColor ;
element . style . fontSize = ` ${ this . fontSize } px` ;
}
}
// Usage
@ Component ({
selector: 'app-demo' ,
standalone: true ,
imports: [ StylerDirective ],
template: `
<p appStyler
[textColor]="'blue'"
[bgColor]="'lightgray'"
[fontSize]="20">
Styled paragraph
</p>
`
})
export class DemoComponent { }
Host Bindings and Listeners
Use host metadata for cleaner directives:
import { Directive , HostBinding , HostListener , Input } from '@angular/core' ;
@ Directive ({
selector: '[appButton]' ,
standalone: true ,
host: {
'class' : 'btn' ,
'[class.btn-primary]' : 'isPrimary' ,
'[attr.role]' : '"button"' ,
'(click)' : 'onClick()'
}
})
export class ButtonDirective {
@ Input () isPrimary = false ;
@ HostBinding ( 'class.disabled' ) @ Input () disabled = false ;
@ HostListener ( 'mouseenter' )
onMouseEnter () {
console . log ( 'Mouse entered' );
}
onClick () {
if ( ! this . disabled ) {
console . log ( 'Button clicked' );
}
}
}
Modern Angular prefers using the host metadata object over @HostBinding and @HostListener decorators.
Directive Composition
Apply multiple directives to a single host:
import { Directive } from '@angular/core' ;
import { HighlightDirective } from './highlight.directive' ;
import { TooltipDirective } from './tooltip.directive' ;
@ Directive ({
selector: '[appComposed]' ,
standalone: true ,
hostDirectives: [
HighlightDirective ,
{
directive: TooltipDirective ,
inputs: [ 'tooltipText: text' ],
outputs: [ 'tooltipShown' ]
}
]
})
export class ComposedDirective { }
Best Practices
Use modern control flow - Prefer @if, @for, @switch over *ngIf, *ngFor, *ngSwitch
Keep directives focused - Single responsibility
Use host metadata - Instead of @HostBinding and @HostListener
Make directives standalone - Better tree-shaking
Provide meaningful selectors - Use attribute selectors with a prefix
Document behavior - Clear documentation for custom directives
Next Steps
Templates Learn template syntax and bindings
Components Understand component architecture