Directives

About

guide/directives

They are classes that add additional behavior to elements in your applications.

Use Angular's built-in directives to manage forms, lists, styles and what users see.

Directives are type of directive that have Templates.

ng g directive [directive-name] [options]

Best Practices

  • Avoid changing elements directly through the DOM like *.nativeElement.style.*, since angular is able to render the Templates without a DOM, for instance when using service workers, then these properties may not be available.

Attribute Directives

Change the appearance or behavior of an element, component, or another directive.

They listen to and modify the behavior of other HTML elements, attributes, properties, and components.

Built-in Attribute Directives

Built-in directives use only public APIs.

They do not have special access to any private APIs that other directives can't access.

NgClass

An old way of adding and removing CSS classes in elements.

@Component({
    template: `
        <!-- Adding 'special' class based on 'isSpecial' value -->
        <div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>

        <!-- Adding multiple classes with object 'multipleClasses', where each key is the class that will be applied based on its value -->
        <div [ngClass]="multipleClass">Div of multiple classes</div>
    `
})
export class Component {
    protected isSpecial = true;

    protected multipleClasses = {
        'className1': true,
        'className2': false,
    }
}

NgStyle

An old way of adding and removing HTML styles in elements.

@Component({
    template: `
        <!-- Adding single inline style -->
        <div [ngStyle]="'font-size': true ? '10px' : '20px'">This div is special</div>

        <!-- Adding multiple classes with object 'multipleClasses', where each key is the class that will be applied based on its value -->
        <div [ngStyle]="multipleStyles">Div of multiple classes</div>
    `
})
export class Component {
    protected isSpecial = true;

    // You may use regular style names, or camelCase names without quotes
    protected multipleStyles = {
        'font-weight': true ? 'bold': 'normal',
        fontSize: false ? '10px' : '20px',
    }
}

NgModel

Use it to display a data property and update that property when the user makes changes. (Adds two-way data binding to HTML form elements)

<label for="example-ngModel">[(ngModel)]:</label>
<input [(ngModel)]="currentItem.name" id="example-ngModel">
<input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">

More examples in Examples.

To apply [(ngModel)] to a non-form built-in element or a thid-party custom component, you have to write a value accessor.

Create the new directive with the CLI command.

By default a directive will have a [appDirective] attribute seletor, but it could be a class or component selector as well.

Inject ElementRef to get access to the host DOM element.

@Directive({
    standalone: true,
    selector: "[appNew]",
})
export class NewDirective {
    // Inject the `ElementRef` to get the reference to the Host DOM element
    constructor(private el: ElementRef) {
        this.el.nativeElement.style.backgroundColor = "yellow";
    }
}
<p appNew>This will have yellow background.</p>

Passing values into an attribute directive

Pass values to the Directive with input() using the same selector name as the variable name.

export class NewDirective {
    // This uses the same variable name as the directive's selector
    appNew = input("yellow");

    constructor(private el: ElementRef) {
        this.el.nativeElement.style.backgroundColor = this.appNew;
    }
}
<p [appNew]="white">This will override the color to white.</p>

Handling user events

You can handle user events or subscribe to event of the DOM with @HostListener().

export class NewDirective {
    constructor(private el: ElementRef) {}

    @HostListener("mouseenter") onMouseEnter() {
        this.highlight(true);
    }

    @HostListener("mouseleave") onMouseLeave() {
        this.highlight();
    }

    private highlight(state = false) {
        this.el.nativeElement.style.backgroundColor = state ? "yellow" : "transparent";
    }
}

Structural Directives

Change the DOM layout by adding and removing DOM elements.

Structural directives can be raw applied to <ng-template> only.

<ng-template [ngIf]=""><p>Paragraph</p></ng-template>
<ng-template newStructuralDirective let-item [selectFrom]="items">
    <p>Paragraph</p>
</ng-template>

Angular expanded the use of them to other HTML elements by the use of shorthand syntax (*).

<p *ngIf="">Paragraph</p>
<p *newStructuralDirective="let item from items">Paragraph</p>

Angular transforms the * into a <ng-template> that hosts the directive and surrounds the element and its descendants.

Built-in Structural Directives

Conditionally creates or disposes of subviews from the template. (Frees up memory and resources)

By default, ngIf prevents display of an element bound to a null value.

<div *ngIf="isActive">Div</div>

To use an else block, must use a <ng-template> block with the help of a LocalReference.

<div *ngIf="isActive; else elseBlock">Div</div>
<ng-template #elseBlock>Div otherwise</ng-template>

Or with a more descriptive way.

<ng-template [ngIf]="isActive" [ngIfElse]="elseBlock">
    <div>Div</div>
</ng-template>

<ng-template #elseBlock>
    <div>Div otherwise</div>
</ng-temaplate>

NgFor

Repeat a node for each item in a list.

@Component({
    template: `
        <div *ngFor="let item of items">
            <span>{{ item.value }}</span>
        </div>
    `,
})
export class Component {
    protected items = [{ value: "Value1" }, { value: "Value2" }];
}

You may pass the item value to a sub component like.

<app-sub-component *ngFor="let item of items" [itemData]="item" />

You can get the list index with let i=index like.

<div *ngFor="let item of items; let i=index">
    <span [number]="i">{{ item.value }}</span>
</div>

You can track items with trackBy.

Angular can change and re-render only those items that have changed, rather than reloading the entire list of items.

The item list must have some sort of unique id field.

@Component({
    template: `
        <div *ngFor="let item of items; trackBy: item.id">
            <span>{{ item.value }}</span>
        </div>
    `,
})
export class Component {
    protected items = [
        { id: 1, value: "Value1" },
        { id: 2, value: "Value2" },
    ];
}

NgSwitch

To display one element among several possible elements, based on a switch condition.

@Component({
    template: `
        <ng-template [ngFor]="let item of items; trackBy: item.id">
            <div [ngSwitch]="item.value">
                <app-sub-component *ngSwitchCase="'Value1'" [itemData]="item" />
                <app-sub-component *ngSwitchCase="'Value2'" [itemData]="item" />
                <span *ngSwitchDefault >Unknown</span>
            </div
        </ng-template>
    `,
})
export class Component {
    protected items = [
        { id: 1, value: "Value1" },
        { id: 2, value: "Value2" },
    ];
}

Create the new directive with the CLI command.

When you write your own structural directives, use the following syntax.

@Directive({
    selector: "[select]",
})
export class SelectDirective {
    private templateRef = inject(TemplateRef);
    private viewContainerRef = inject(ViewContainerRef);
    selectFrom = input.required<DataSource>();
    
    async ngOnInit() {
        const data = await this.selectFrom.load();
        this.viewContainerRef.createEmbeddedView(this.templateRef, {
            /*
                Create the embedded view with a context object that contains
                the data via the key `$implicit`.
            */
            $implicit: data,
        });
    }
}
<ng-template selector let-item [selectFrom]="items">
    <p>The data is: {{ item.value }}</p>
</ng-template>

Adding Directives to a Component with hostDirectives

You can assign directives to a component through the hostDirectives property inside the component's decorator.

@Component({
    hostDirectives: [MenuBehavior],
})

By default, host directive inputs and outputs are not exposed as part of the component's public API.

Execution order

Including inputs and outputs

By explicitly specifying the inputs and outputs, consumers of the component with hostDirective can bind them in a template.

@Component({
    hostDirectives: [{
        directive: MenuBehavior,
        inputs: ['menuId'],
        outputs: ['menuClosed']
    }],
})
export class AdminMenu { ... }
<admin-menu menuId="top-menu" (menuClosed)="logMenuClosed()">

Alias the inputs and outputs

You can also alias them to customize the API of your component.

@Component({
    hostDirectives: [{
        directive: MenuBehavior,
        inputs: ['menuId: id'],
        outputs: ['menuClosed: closed']
    }],
})
export class AdminMenu { ... }
<admin-menu id="top-menu" (closed)="logMenuClosed()">

Adding Directives to other Directives with hostDirectives

You can also add hostDirectives to other directives, in addition to components.

This enables the transitive aggregation of multiple behaviors (chainned directives).

@Directive({ ... })
export class Menu { ... }

@Directive({ ... })
export class Tooltip { ... }

// MenuWithTooltip can compose behaviors from multiple other directives
@Directive({
    hostDirectives: [Tooltip, Menu],
})
export class MenuWithTooltip { ... }

// CustomWidget can apply the already-composed behaviors from MenuWithTooltip
@Directive({
    hostDirectives: [MenuWithTooltip],
})
export class SpecializedMenuWithTooltip { }

Last updated