DEV Community

GaurangDhorda
GaurangDhorda

Posted on

Custom Directive implementation like *ngIf is!

Hello to this little introduction about Custom directives in angular.

What is Directive?

In angular a directive is special kind of component but without any template referencing directly. Meaning A component is directive with template binding out of the box. A directive can be useful for any DOM manipulation in application. In fact, angular is recommending to use custom-directive when you want to safely manipulate DOM.

Types of directive?
  1. Component directive. any component in angular with @Component decorator is special kind of directive, and we called it as a component directive.
  2. Attribute directive. Angular provides [ngClass], [ngStyle] which are useful for changing appearance of element.
  3. Structural directive. Angular provides *ngIf, *ngFor, *ngSwitch are all called as structural directive because of all are used to manipulate DOM structure by adding or removing element directly.
  4. Custom directive. this is directive we can used in angular for custom DOM logic implementation. we can create custom directive using angular-cli by firing ng generate directive <directive-name> and custom directive is generated with @Direvtive() decorator in class. By default scope is ngModule level.

Today, we are going to learn how to implement our own *ngIf using custom-directive.

now lets create custom directive by firing this command..

ng generate directive custom-directive-if
Enter fullscreen mode Exit fullscreen mode

Above command will generate directive like this..

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

     @Directive({
       selector: '[appCustomDirectiveIf]'
     })
     export class CustomDirectiveIfDirective {
      constructor() { }
     }
Enter fullscreen mode Exit fullscreen mode

now lets create add below code to app.component.html

    <div class="row p-1">
      <div class="col-6 d-flex align-items-center">
        <input #checkboxValue id="checkBox" type="checkbox" (change)="onCheckboxChanged(checkboxValue)">
        <label class="ml-1 cursor" for="checkBox"> show/hide </label>
      </div>
      <div *appCustomDirectiveIf="show" class="col-6 shadow-color-blue">
        Custom If directive content displayed...      
      </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

Above code note we are using our own custom implementation of directive to replace *ngIf or understand properly how to manipulate DOM node properly. we are using *appCustomDirectiveIf and passing reference of show to it which is coming from checkbox. When user checked checkbox show becomes true by calling (change) event of input type="checkbox", so we call onCheckboxChanged() and passes reference of input-checkbox. Then after checkbox value is passed to out custom directive as a @Input().

now implement custom-directive

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

    @Directive({
      selector: '[appCustomDirectiveIf]'
    })
    export class CustomDirectiveIfDirective {
      @Input()
      set appCustomDirectiveIf(show: boolean){
          show ? this.container.createEmbeddedView(this.templateRef) : this.container.clear();
      }
      constructor(private templateRef: TemplateRef<any>,
                  private container: ViewContainerRef) { }
    }
Enter fullscreen mode Exit fullscreen mode

We are injecting 1. TemplateRef. TemplateRef is the one we applied our custom directive in template. means the template node reference on which we are applying custom-directive.

  1. ViewContainerRef. In angular we are not directly manipulate DOM or accessing DOM structure. Because angular is plateform independent meaning same code base you can use in ng-Universal or in IONIC. SO, accessing DOM directly you break code to run in other plateform where DOM is not available. So to safely access DOM structure angular creates their own VIEW hierarchy and based on that DOM is created or removed. To access VIEW hierarchy angular provide ViewContainerRef, and some methods to add or remove element from view and view directly bounded to DOM so it will update DOM for us automatically.

Now, when we pass true to @Input() view.createEmbeddedView() method is called and it will create new DOM node element in current element hierarchy. and if value is false then we clear out view hierarchy and DOM updates occurs too.

You can find out working code in this link

Top comments (3)

Collapse
 
dingalla profile image
Dan Ingalla

This is adding multiple instances of the component when the condition changes for me. Any idea why? If it matters, the condition is an observable

Collapse
 
diego_ferreira_63e91d9df9 profile image
Diego Ferreira

I'm facing the same issue now. The only solution I found was adding a control variable like isHidden: boolean and changing its value whenever I hide or show the element. By doing this, when I'm about to show the element in case the condition is true, I validade the value of this variable first.

if (showElement) {
  if (this.isHidden) {
    this.viewContainer.createEmbeddedView(this.templateRef);
    this.isHidden = false;
  }
} else {
  this.viewContainer.clear();
  this.isHidden = true;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
gaurangdhorda profile image
GaurangDhorda • Edited

What if we do like this ... First we clear viewContainer regardless of condition of show variable. Then, we create new view based on show true case only?

this.viewContainer.clear();
if(show){
    this.viewContainer.createEmbeddedView(this.templateRef);
}
Enter fullscreen mode Exit fullscreen mode