Structural directives in Angular are used to add or remove elements from the DOM based on specific conditions. The most common examples of Angular’s built-in structural directives are *ngIf
, *ngFor
, and *ngSwitch
, each using the *
syntax to dynamically control rendering. But did you know you can create custom structural directives to suit your application’s unique needs? This article walks you through the process of creating custom structural directives in Angular, including practical examples and best practices.
What is a Structural Directive?
A structural directive is a directive that changes the structure of the DOM by adding or removing elements based on certain conditions. Angular’s structural directives use a syntax with the *
symbol (e.g., *ngIf
) as shorthand for transforming the directive into an ng-template
. This shorthand enables a concise and intuitive syntax for developers to conditionally render or iterate over elements.
Key Elements of a Structural Directive
To create a custom structural directive, you need to understand a few essential parts:
TemplateRef
: Represents a chunk of HTML, including child elements, which Angular renders conditionally. This is used in structural directives to define the template that can be added or removed from the DOM.ViewContainerRef
: Acts as a placeholder for view creation, providing methods to add or remove views (or elements) dynamically.@Directive
Decorator: Defines the directive and applies it to the host element using a selector.
Step-by-Step Guide: Creating a Custom Structural Directive
Let’s create a custom structural directive called *appUnless
, which will behave as the inverse of *ngIf
. This directive will only render its content if a specified condition is false
.
Step 1: Generate the Directive
Use Angular CLI to generate the directive boilerplate. Open your terminal and run the following command:
bashCopy codeng generate directive unless
This command creates a directive file called unless.directive.ts
with a basic setup.
Step 2: Implement the Directive Logic
Open unless.directive.ts
and modify it as follows:
typescriptCopy codeimport { 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) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
In this example:
@Directive
: Defines the directive with the selectorappUnless
, which is applied as*appUnless
.- Constructor: Injects
TemplateRef
andViewContainerRef
:TemplateRef
represents the template that Angular will render.ViewContainerRef
provides methods to control the rendering of the template.
@Input()
: TheappUnless
property is set as an input to the directive, which allows us to bind a condition to it.
The appUnless
setter:
- Creates a view if the condition is
false
and no view is currently rendered. - Clears the view if the condition is
true
and a view is currently rendered.
Step 3: Using the Custom Structural Directive in a Component
You can now use *appUnless
in any component template. Here’s an example in a component template:
htmlCopy code<div *appUnless="isLoggedIn">
You must log in to see this content.
</div>
<div *appUnless="items.length">
No items available.
</div>
In this template:
- The first
div
element renders only whenisLoggedIn
isfalse
. - The second
div
element renders only whenitems
is empty.
Explanation of the appUnless
Directive Logic
The logic inside appUnless
works as follows:
- When
appUnless
isfalse
, Angular callsviewContainer.createEmbeddedView()
, which adds the template to the DOM. - When
appUnless
istrue
, Angular callsviewContainer.clear()
, which removes the template from the DOM.
Step 4: Testing the Directive
To test the directive, add the following code to your component:
typescriptCopy codeexport class SomeComponent {
isLoggedIn = false;
items = [];
toggleLogin() {
this.isLoggedIn = !this.isLoggedIn;
}
addItem() {
this.items.push('New Item');
}
clearItems() {
this.items = [];
}
}
Then, add buttons to your template to control the isLoggedIn
and items
values:
htmlCopy code<button (click)="toggleLogin()">Toggle Login</button>
<button (click)="addItem()">Add Item</button>
<button (click)="clearItems()">Clear Items</button>
<div *appUnless="isLoggedIn">
You are not logged in.
</div>
<div *appUnless="items.length">
No items available.
</div>
Now, clicking the buttons will dynamically show or hide content based on the conditions in *appUnless
.
Advanced Custom Structural Directive Examples
Let’s explore more complex custom directives to understand how TemplateRef
and ViewContainerRef
offer powerful tools for Angular development.
Example 1: Creating a *appRepeat
Directive
Imagine you want to create a directive that repeats a template a specific number of times. Here’s how to do it.
Step 1: Generate the Directive
bashCopy codeng generate directive repeat
Step 2: Implement the Directive Logic
Edit repeat.directive.ts
as follows:
typescriptCopy codeimport { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appRepeat]'
})
export class RepeatDirective {
@Input() set appRepeat(times: number) {
this.viewContainer.clear();
for (let i = 0; i < times; i++) {
this.viewContainer.createEmbeddedView(this.templateRef, { index: i });
}
}
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
}
In this directive:
- Loop: The
appRepeat
input property receives a number,times
, that controls the number of times to repeat the template. - createEmbeddedView(): Creates a new instance of the template for each loop iteration, using
index
as a context variable.
Step 3: Using the *appRepeat
Directive
You can now use *appRepeat
like this:
htmlCopy code<p *appRepeat="5; let i = index">This is item #{{ i + 1 }}</p>
This will render the template five times, each time displaying a unique index.
Example 2: Creating a *appShowAfterDelay
Directive
This directive will display content only after a specified delay, which can be useful for loaders or timed messages.
Step 1: Generate the Directive
bashCopy codeng generate directive showAfterDelay
Step 2: Implement the Directive Logic
Edit show-after-delay.directive.ts
:
typescriptCopy codeimport { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appShowAfterDelay]'
})
export class ShowAfterDelayDirective {
@Input() set appShowAfterDelay(delay: number) {
this.viewContainer.clear();
setTimeout(() => {
this.viewContainer.createEmbeddedView(this.templateRef);
}, delay);
}
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
}
Here:
- Delay: The directive accepts a
delay
in milliseconds. - setTimeout:
setTimeout
delays the rendering of the template by the specified duration.
Step 3: Using the *appShowAfterDelay
Directive
htmlCopy code<p *appShowAfterDelay="3000">This text appears after 3 seconds.</p>
This will display the paragraph after a 3-second delay.
Best Practices for Creating Custom Structural Directives
- Use Descriptive Names: Use names that reflect the purpose of the directive (e.g.,
*appUnless
for conditions,*appRepeat
for looping). - Limit Complexity: Keep the directive logic focused. Complex conditions or data processing are best handled in the component.
- Optimize for Reusability: Build directives that are reusable across different components and contexts.
- Utilize Context Objects: Use context objects with
ng-template
to pass dynamic data and enhance flexibility.
Conclusion
Custom structural directives in Angular are a powerful way to extend the framework’s built-in capabilities, allowing you to manipulate the DOM dynamically based on various conditions. By mastering concepts like TemplateRef
, ViewContainerRef
, and Angular’s @Directive
decorator, you can create concise, efficient, and reusable structural directives tailored to your application’s needs.
Whether you’re adding conditional rendering with *appUnless
, creating loops with *appRepeat
, or delaying display with *appShowAfterDelay
, custom structural directives can simplify your code and make it more expressive. With these techniques, you’re equipped to create dynamic, user-friendly, and maintainable Angular applications