DEV Community

Cover image for Build Custom Structural Directives in Angular like a hero 😎
Deekshith Raj Basa 🔥
Deekshith Raj Basa 🔥

Posted on

Build Custom Structural Directives in Angular like a hero 😎

Introduction

Angular comes with some built-in directives.

Structural Directive can be used to manipulate the HTML structure in the DOM. Using them, we can change the structure of part of the DOM.

  1. *ngIf
  2. *ngForOf
  3. *ngSwitch

Creating a custom structural directive

Why custom structural directive?

We have build in structural directive that satisfies our solution, what if we might come across a case that the ones provided with the framework don't solve. so here comes the custom structural directive.

So, in this article we will try cloning the *ngIf structural directive

Let's understand how we can create *ngIf structural directive

Let's create a project using Angular CLI

// use this command to create new angular project
ng new project-name
Enter fullscreen mode Exit fullscreen mode
// create a module 
ng generate module custom-if
Enter fullscreen mode Exit fullscreen mode

Now let's create a custom directive

// create a custom directive
ng generate directive
Enter fullscreen mode Exit fullscreen mode

generated directive should look like this

import { Directive } from '@angular/core';

@Directive({
  selector: '[customIf]',
})
export class customIfDirective {
  constructor() {}
}
Enter fullscreen mode Exit fullscreen mode

Let's implement the basic functionality of displaying the content if passed value is true

<h2 *customIf="true">My visible content</h2>
<h2 *customIf="false">My hidden content</h2>
Enter fullscreen mode Exit fullscreen mode

To achieve that, we need a couple of elements:

an input that will determine whether to show or hide the content (@Input)

a reference to the template that we want to conditionally display (TemplateRef)

a container that will provide us with access to Angular's view (ViewContainerRef)

The @input can be just a regular class field decorators property with Angular's. For it to work as it does in the example code shown*customIf="true", we need to name the property the same as the attribute's selector:

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

@Directive({
  selector: '[customIf]',
})
export class IfDirective {
 @Input() set customIf(show: boolean) {
    //code goes here
  }

  constructor() {}
}
Enter fullscreen mode Exit fullscreen mode

Now the directive has the value to display the content, we also need TemplateRef and ViewContainerRef instances. We can do that by injecting them by importing from @angular/core:

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

constructor(
    private templateRef: TemplateRef<unknown>,
    private vcr: ViewContainerRef
  ) {}
Enter fullscreen mode Exit fullscreen mode

Now, we can make a use of ViewContainerRef's reference this.vcr.createEmbeddedView(this.templateRef) method to display and this.vcr.clear() method to remove the content.

This is how the final code looks like

@Directive({
  selector: '[customIf]',
})
export class IfDirective {
@Input() set customIf(value: boolean) {
    this._renderTemplate(value)l
  }

constructor(
    private templateRef: TemplateRef<unknown>,
    private vcr: ViewContainerRef
  ) {}

private _renderTemplate(show: boolean) {
    this.vcr.clear();
    if (show) {
      this.vcr.createEmbeddedView(this.templateRef);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Wohoo! 🥳 we have successfully create *customIf now let's focus on creating else template
So, let us understand how it works?

How do we achieve this behaviour in our custom directive? This is where understanding the "syntactic sugar" that stands behind the structural directives is crucial.

Custom Structural Directives

If we observe the above image example, it means that the else property is actually becoming ngIfElse input parameter.

So, we can access the else template by selector (customIf) + else (Else) = customIfElse

@Input() customIfElse?: TemplateRef<unknown>;
Enter fullscreen mode Exit fullscreen mode

Now, the code looks like

@Directive({
  selector: '[customIf]',
})
export class IfDirective {
@Input() set customIf(value: boolean) {
    this._renderTemplate(value)l
  }
@Input() customIfElse?: TemplateRef<unknown>;
constructor(
    private templateRef: TemplateRef<unknown>,
    private vcr: ViewContainerRef
  ) {}

private _renderTemplate(show: boolean) {
    this.vcr.clear();
    if (show) {
      this.vcr.createEmbeddedView(this.templateRef);
    } else if (this.customIfElse) {
      this.vcr.createEmbeddedView(this.customIfElse);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary

In this article, we've learnt how to create a simple custom structural directive that handles additional inputs. We've covered the syntactic sugar that stands behind the structural directive, and how it translates into directive's inputs.

In case you have any questions, you can always tweet or DM me at @DeekshithrajB. I'm always happy to help!

Connect with me over linkedIn: Deekshith Raj Basa

Top comments (1)

Collapse
 
hackpoint profile image
HackP0!nt

Hi thank you for the post. Can you extend the code demoes with stackblitz.com and for example how's customIf handles streams (rxjs) seems it wont work with not-resolved arguments.