Directives and Create Structural Directive in Angular

Sinan Öztürk
4 min readJan 28, 2024

--

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

Directive Types

  1. Components: These directives contains a template. ( Will not be part of this post )
  2. Attribute directives: Change the appearance or behavior of an element, component, or another directive.
  3. Structural directives: Change the DOM layout by adding and removing DOM elements. All structural Directives are preceded by Asterix symbol.

Attribute Directives

Apply attribute directives to elements to change appearance or behaviour. Angular has some built-in attribute directives ( NgClass, NgStyle, NgModel).

See an custom attribute directive example;

import { Directive, ElementRef, OnInit, inject } from '@angular/core';

@Directive({
selector: '[appColor]',
standalone: true,
})
export class ColorDirective implements OnInit {
elementRef = inject(ElementRef);
ngOnInit(): void {
this.elementRef.nativeElement.style.color = 'red';
}
}
  • Now apply directive to the element.
import { Component } from '@angular/core';
import { ColorDirective } from './color.directive';

@Component({
selector: 'app-root',
standalone: true,
imports: [ColorDirective],
template: `
<p appColor>Sinan997</p>
`,
})
export class AppComponent {}

In summary, when you apply appColor directive to p element, you can reach the pelement via ElementRef in Directive class and manipulate the element.

You can even listen the mouse events on the element.

import { Directive, ElementRef, HostListener, inject } from '@angular/core';

@Directive({
selector: '[appColor]',
standalone: true,
})
export class ColorDirective {
elementRef = inject(ElementRef);
@HostListener('mouseenter') onMouseEnter() {
this.setColor('red');
}
@HostListener('mouseleave') onMouseLeave() {
this.setColor('yellow');
}
setColor(color: string) {
this.elementRef.nativeElement.style.color = color;
}
}

You can pass value into an attribute directive.

import { Directive, ElementRef, Input, OnInit, inject } from '@angular/core';

@Directive({
selector: '[appColor]',
standalone: true,
})
export class ColorDirective implements OnInit {
elementRef = inject(ElementRef);
@Input() appColor?: string;
ngOnInit(): void {
if (this.appColor) {
this.elementRef.nativeElement.style.color = this.appColor;
}
}
}

Apply to element

<p [appColor]="'purple'">Sinan997</p>
  • If you want to define another input for directive, it must be used after the appColor selector. Othervise it wont be detected by directive.

Structural Directives

Structural directives are directives which change the DOM layout by adding and removing DOM elements. Angular has built-in structural directives (NgIf, NgForOf, NgSwitch)

Structural directives uses * prefix. This is a shortcut for structural directives. Under the hood Angular converts this to longer form. It surrounds the host element and its content with <ng-template>.

Which means <ng-template> is accessable in directive’s class via TemplateRef.

By getting reference of the template, you can add it to the DOM whenever you want.

How angular converts * prefix to longer form

*ngIf

<p *ngIf="showMe" class="text-primary">Sinan997</p>

<!-- converts to -->
<ng-template [ngIf]="showMe">
<p class="text-primary"></p>
</ng-template>

*ngFor

<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
class="d-flex">
<p class="text-primary">{{i}} Sinan</p>
<p class="text-secondary">997</p>
</div>

<!-- converts to -->

<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"
[ngForTrackBy]="trackById">
<div class="d-flex">
<p class="text-primary">{{i}} Sinan</p>
<p class="text-secondary">997</p>
</div>
</ng-template>
  • Everything in structural directive is moved to the <ng-template>. All other attributes, bindings are goes with element.
  • let keyword declares a template input variable.

One structural directive per element

  • You can’t apply more than one structural directive to the same element.
<div *ngIf="showMe" *ngFor="let number of [1,2,3]">
{{number}} Sinan997
</div>

// WILL THROW THIS ERROR
/**
* Can't have multiple template bindings on one element.
* Use only one attribute prefixed with.
*/


// CONVERT TO THIS
<ng-container *ngIf="showMe">
<div *ngFor="let number of [1,2,3]">
{{number}} Sinan997
</div >
</ng-container>

Creating a structural directive

<ng-template> is lazy and it wont render to DOM by its own. It must be explicitly added to the DOM.

Lets create an simple custom ngIf structural directive.

conditional-render.directive.ts

import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angular/core';

@Directive({
selector: '[conditionalRender]',
standalone: true
})
export class ConditionalRenderDirective {
protected readonly templateRef = inject(TemplateRef);
protected readonly viewContainer = inject(ViewContainerRef);

@Input({ required: true }) conditionalRender: boolean;
@Input() conditionalRenderOther: TemplateRef<any>;

ngOnChanges() {
this.viewContainer.clear();
if (this.conditionalRender) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.createEmbeddedView(this.conditionalRenderOther);
}
}
}

app.component.ts

import { Component } from '@angular/core';
import { ColorDirective } from './color.directive';
import { CommonModule } from '@angular/common';
import { ConditionalRenderDirective } from './conditional-render.directive';

@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, ColorDirective, ConditionalRenderDirective],
template: `
<div
class="alert alert-primary"
*conditionalRender="booleanValue; other: elseContent"
>
If condition is TRUE i will be shown.
</div>

<button class="btn btn-primary mt-3" (click)="booleanValue = !booleanValue">
Change condition
</button>

<ng-template #elseContent>
<div class="alert alert-primary">
If condition is FALSE i will be shown.
</div>
</ng-template>
`,
})
export class AppComponent {
booleanValue = false;
}

--

--