Search code examples
angularangular-directive

Angular Directive call multiple times


tree-view.html

<div *ngFor="let item of nodes; let i=index" class="parent" [id]="item.name" appTreeView>
        {{item.name}}
        <div class="child" *ngIf="item.child">
            <app-tree-view [nodes]="item.child"></app-tree-view>  // recursive call 
        </div>
</div>

treeview.ts

this.data=[
      {
        name:"level 1",
        child:[
          {
            name:"level 1.1",
          }
        ]
      },
      {
        name:"level 2",
        child:[
          {
            name:"level 2.1",
            child:[
              {
                name:"level 2.1.1"
              }
            ]
          }
        ]
      }
    ]

directive.ts

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

@Directive({
  selector: '[appTreeView]',
})
export class TreeViewDirective {

  constructor(private render:Renderer2) { }
  @HostListener('click',['$event'])
  check(event){
    console.log(event.target.id);
    const avalclass:string=event.target.classList.value;
    if(avalclass.includes('expand')){
      this.render.removeClass(event.target,'expand');
    }else{
      this.render.addClass(event.target,'expand');
    }
    
  }
}

Output

  • Level 1
  • Level 2

If I click level 2 expand sub menu level 2.1 console print one time

  • Level 1
  • Level 2
    • Level 2.1

then i click level 2.1 expand sub menu 2.1.1 console print two times

  • Level 1
  • Level 2
    • Level 2.1
      • Level 2.1.1

Why console print multiple times. what is my mistakes


Solution

  • Console prints multiple times the same because of you are using recursion to call a parent and his descendants. You are binding a click event multiple times depending on what level is the element where you're clicking on. If you are aware of the flow of your code:

    • If you click in a first level item (Level 1 or Level 2): it prints one time.
    • If you click in a second level item (Level 1.1 or Level 2.2): it prints two times, one for the second level and other for the first one.
    • And so on..

    I've prepared a demo with the code that you put in your question. You can see in the console who is calling the event and who is the host element each time.

    https://stackblitz.com/edit/angular-recursion-directives?file=src/app/tree-view.directive.ts

    You could solve that adding a flag to your directive and checking it in your check function:

    if (this.isFired) {
          return;
        }
    this.isFired = true;
    

    Or comparing the current host element and the element that triggered the event:

    if(this.el.nativeElement.id == event.target.id){
       console.log("click on: " + event.target.id);
          const avalclass: string = event.target.classList.value;
    
          if (avalclass.includes("expand")) {
            this.render.removeClass(event.target, "expand");
          } else {
            this.render.addClass(event.target, "expand");
          }
        }
    }