Angular Directives
Introduction
Angular directives are special instructions in the DOM (Document Object Model) that tell Angular how to render a template. They are one of the most powerful features in Angular, allowing you to extend HTML with new attributes and elements, and to manipulate the DOM structure.
In essence, directives are markers on DOM elements that Angular can detect and attach specific behaviors to. They allow you to create reusable components, apply conditional logic to your templates, manipulate DOM elements, and much more.
Types of Directives in Angular
Angular provides three types of directives:
- Component Directives: These are directives with a template. Every Angular component is technically a directive.
- Structural Directives: These change the DOM layout by adding or removing elements. They are prefixed with an asterisk (
*
). - Attribute Directives: These change the appearance or behavior of an existing element.
Let's explore each type in detail.
Built-in Directives
Structural Directives
Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure by adding, removing, and manipulating elements.
1. *ngIf
*ngIf
conditionally includes a template based on the evaluation of an expression.
// component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-conditional',
template: `
<div *ngIf="isVisible">
This content will be visible if isVisible is true.
</div>
<button (click)="toggleVisibility()">Toggle Visibility</button>
`
})
export class ConditionalComponent {
isVisible = true;
toggleVisibility() {
this.isVisible = !this.isVisible;
}
}
When you click the button, the content will toggle between being visible and hidden.
2. *ngFor
*ngFor
repeats a portion of the DOM tree once for each item in an iterable.
// component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-list',
template: `
<ul>
<li *ngFor="let item of items; let i = index">
{{ i + 1 }}. {{ item }}
</li>
</ul>
`
})
export class ListComponent {
items = ['Apple', 'Banana', 'Cherry', 'Date'];
}
Output:
1. Apple
2. Banana
3. Cherry
4. Date
The *ngFor
directive also provides additional variables:
index
: The position of the current item in the iterable (0-based)first
: Boolean indicating if the current item is the first itemlast
: Boolean indicating if the current item is the last itemeven
: Boolean indicating if the current index is evenodd
: Boolean indicating if the current index is odd
3. *ngSwitch
*ngSwitch
conditionally swaps the contents of the element it's attached to:
// component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-switch-example',
template: `
<div>
<select [(ngModel)]="selected">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
<div [ngSwitch]="selected">
<p *ngSwitchCase="'option1'">You selected Option 1</p>
<p *ngSwitchCase="'option2'">You selected Option 2</p>
<p *ngSwitchCase="'option3'">You selected Option 3</p>
<p *ngSwitchDefault>Please select an option</p>
</div>
</div>
`
})
export class SwitchExampleComponent {
selected = '';
}
Here, the content displayed will depend on which option is selected.
Attribute Directives
Attribute directives alter the appearance or behavior of an existing element.
1. ngClass
ngClass
adds or removes CSS classes:
// component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-class-example',
template: `
<div [ngClass]="{'text-red': isRed, 'bold-text': isBold}">
This text can be red and bold.
</div>
<button (click)="toggleRed()">Toggle Red</button>
<button (click)="toggleBold()">Toggle Bold</button>
`,
styles: [`
.text-red { color: red; }
.bold-text { font-weight: bold; }
`]
})
export class ClassExampleComponent {
isRed = false;
isBold = false;
toggleRed() {
this.isRed = !this.isRed;
}
toggleBold() {
this.isBold = !this.isBold;
}
}
2. ngStyle
ngStyle
applies inline styles:
// component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-style-example',
template: `
<div [ngStyle]="{'color': textColor, 'font-size.px': fontSize}">
This text has dynamic styling.
</div>
<button (click)="changeColor()">Change Color</button>
<button (click)="increaseSize()">Increase Size</button>
`
})
export class StyleExampleComponent {
textColor = 'blue';
fontSize = 16;
colors = ['blue', 'red', 'green', 'purple', 'orange'];
colorIndex = 0;
changeColor() {
this.colorIndex = (this.colorIndex + 1) % this.colors.length;
this.textColor = this.colors[this.colorIndex];
}
increaseSize() {
this.fontSize += 2;
}
}
3. ngModel
ngModel
provides two-way data binding:
// component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-model-example',
template: `
<input [(ngModel)]="name" placeholder="Enter your name">
<p>Hello, {{ name }}!</p>
`,
imports: [FormsModule] // Import FormsModule in standalone component
})
export class ModelExampleComponent {
name = '';
}
Note: For non-standalone components, you need to import FormsModule
in your module:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { ModelExampleComponent } from './model-example.component';
@NgModule({
imports: [BrowserModule, FormsModule],
declarations: [AppComponent, ModelExampleComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
Creating Custom Directives
You can create your own directives to implement custom behavior. Let's create a simple highlight directive that changes the background color of an element when hovered.
Custom Attribute Directive
// highlight.directive.ts
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input('appHighlight') highlightColor = 'yellow';
@Input() defaultColor = '';
constructor(private el: ElementRef) { }
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || 'yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(this.defaultColor);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
Using the directive in a component:
// component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-directive-demo',
template: `
<p [appHighlight]="'orange'" [defaultColor]="'lightgray'">
Hover over me to see the highlight directive in action!
</p>
`
})
export class DirectiveDemoComponent { }
Don't forget to declare the directive in your module (or include it in the imports
array if using a standalone directive):
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { DirectiveDemoComponent } from './directive-demo.component';
import { HighlightDirective } from './highlight.directive';
@NgModule({
imports: [BrowserModule],
declarations: [
AppComponent,
DirectiveDemoComponent,
HighlightDirective
],
bootstrap: [AppComponent]
})
export class AppModule { }
Custom Structural Directive
Creating a structural directive is more complex. Let's create a simple *appUnless
directive that works opposite to *ngIf
:
// unless.directive.ts
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) {
// If condition is false and view hasn't been created yet,
// create the view
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
// If condition is true and view exists,
// destroy the view
this.viewContainer.clear();
this.hasView = false;
}
}
}
Using the custom structural directive:
// component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-unless-demo',
template: `
<p *appUnless="condition">
This paragraph is displayed only when the condition is false.
</p>
<button (click)="toggleCondition()">Toggle Condition</button>
`
})
export class UnlessDemoComponent {
condition = false;
toggleCondition() {
this.condition = !this.condition;
}
}
Real-World Example: Tooltip Directive
Let's create a more practical custom directive - a tooltip that appears when hovering over an element:
// tooltip.directive.ts
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';
@Directive({
selector: '[appTooltip]'
})
export class TooltipDirective {
@Input('appTooltip') tooltipText = '';
private tooltip: HTMLElement | null = null;
constructor(private el: ElementRef, private renderer: Renderer2) { }
@HostListener('mouseenter') onMouseEnter() {
if (!this.tooltip) {
this.createTooltip();
}
}
@HostListener('mouseleave') onMouseLeave() {
if (this.tooltip) {
this.renderer.removeChild(document.body, this.tooltip);
this.tooltip = null;
}
}
private createTooltip() {
this.tooltip = this.renderer.createElement('div');
this.renderer.appendChild(document.body, this.tooltip);
// Set tooltip text
this.renderer.setProperty(this.tooltip, 'textContent', this.tooltipText);
// Add CSS to the tooltip
this.renderer.setStyle(this.tooltip, 'position', 'absolute');
this.renderer.setStyle(this.tooltip, 'background-color', 'black');
this.renderer.setStyle(this.tooltip, 'color', 'white');
this.renderer.setStyle(this.tooltip, 'padding', '5px 10px');
this.renderer.setStyle(this.tooltip, 'border-radius', '4px');
this.renderer.setStyle(this.tooltip, 'z-index', '1000');
// Position the tooltip
const hostPos = this.el.nativeElement.getBoundingClientRect();
const top = hostPos.top - 30; // Position above the element
const left = hostPos.left + hostPos.width / 2; // Center horizontally
this.renderer.setStyle(this.tooltip, 'top', `${top}px`);
this.renderer.setStyle(this.tooltip, 'left', `${left}px`);
this.renderer.setStyle(this.tooltip, 'transform', 'translateX(-50%)'); // Center the tooltip
}
}
Using the tooltip directive:
// component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-tooltip-demo',
template: `
<button [appTooltip]="'This is a tooltip'">
Hover over me to see a tooltip
</button>
`
})
export class TooltipDemoComponent { }
Summary
Angular directives are a powerful feature that extend HTML capabilities. We've explored:
- Structural directives (
*ngIf
,*ngFor
,*ngSwitch
): Change the DOM layout by adding, removing, or manipulating elements - Attribute directives (
ngClass
,ngStyle
,ngModel
): Change the appearance or behavior of existing elements - Custom directives: Allow you to create reusable functionality
Directives are one of Angular's core features, allowing developers to create reusable HTML logic and enabling the framework to create dynamic, responsive user interfaces. By mastering directives, you can significantly enhance the functionality and interactivity of your Angular applications.
Additional Resources
- Angular Official Documentation on Directives
- Angular Structural Directives
- Building Custom Directives
Exercises
- Create a custom structural directive that displays content only on specific days of the week.
- Build an attribute directive that formats currency inputs automatically.
- Implement a directive that allows drag and drop functionality on elements.
- Create a directive that validates input fields and displays error messages.
- Build a directive that animates elements when they enter the viewport.
These exercises will help you understand directives better and improve your Angular development skills.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)