Skip to main content

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:

  1. Component Directives: These are directives with a template. Every Angular component is technically a directive.
  2. Structural Directives: These change the DOM layout by adding or removing elements. They are prefixed with an asterisk (*).
  3. 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.

typescript
// 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.

typescript
// 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 item
  • last: Boolean indicating if the current item is the last item
  • even: Boolean indicating if the current index is even
  • odd: Boolean indicating if the current index is odd

3. *ngSwitch

*ngSwitch conditionally swaps the contents of the element it's attached to:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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

typescript
// 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:

typescript
// 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):

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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

Exercises

  1. Create a custom structural directive that displays content only on specific days of the week.
  2. Build an attribute directive that formats currency inputs automatically.
  3. Implement a directive that allows drag and drop functionality on elements.
  4. Create a directive that validates input fields and displays error messages.
  5. 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! :)