Angular

How to Create a Custom Structural Directive in Angular

Here is the visual diagram illustrating a custom structural directive in Angular, showing the process of creating a directive, defining TemplateRef and ViewContainerRef, and using the directive with star (*) syntax in a template. It includes labeled sections such as "Define Directive," "Inject TemplateRef and ViewContainerRef," "Implement Logic," and "Use Directive in Template," with example code snippets and annotations for clear understanding.

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:

  1. 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.
  2. ViewContainerRef: Acts as a placeholder for view creation, providing methods to add or remove views (or elements) dynamically.
  3. @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 selector appUnless, which is applied as *appUnless.
  • Constructor: Injects TemplateRef and ViewContainerRef:
    • TemplateRef represents the template that Angular will render.
    • ViewContainerRef provides methods to control the rendering of the template.
  • @Input(): The appUnless 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 when isLoggedIn is false.
  • The second div element renders only when items is empty.

Explanation of the appUnless Directive Logic

The logic inside appUnless works as follows:

  • When appUnless is false, Angular calls viewContainer.createEmbeddedView(), which adds the template to the DOM.
  • When appUnless is true, Angular calls viewContainer.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

  1. Use Descriptive Names: Use names that reflect the purpose of the directive (e.g., *appUnless for conditions, *appRepeat for looping).
  2. Limit Complexity: Keep the directive logic focused. Complex conditions or data processing are best handled in the component.
  3. Optimize for Reusability: Build directives that are reusable across different components and contexts.
  4. 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

Leave a Reply

Your email address will not be published. Required fields are marked *