Search code examples
angularviewchild

Angular - Function linked to ViewChild executing twice


I have three components, Nav, App, Form. In the Nav component I have a function that changes the position of CSS, I can call this function from Nav and App Component (Gets a trigger from Form Component). My problem is that when I call the function from NAV component, it triggers twice.

I tried removing the @ViewChild and that fixed the problem but wouldn't work how I want. I looked around and I found that I should use stopPropagation, but all the examples had event.StopPropgation and I don't understand how to apply that to my functions.

Html for Navigation

    <div class="navButton add" (click)="addFormToggle()">
      <div class="bar vertical"></div>
      <div class="bar horizontal"></div>
    </div>

TS For Navigation

When the button is clicked, it's function fires the CSS change function and another function to render the form Component.

import { Component, OnInit, Renderer2, ElementRef, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'menu-navigation',
  templateUrl: './menu-navigation.component.html',
  styleUrls: ['./menu-navigation.component.scss']
})
export class MenuNavigationComponent implements OnInit {

  constructor(private renderer: Renderer2, private el: ElementRef, private ref: ChangeDetectorRef) { }
  ngOnInit() { }

  @Output() addFormToggleEvent = new EventEmitter<Event>();

  addToggle;

  addFormIconToggle() {
    this.addToggle = !this.addToggle;

    let vert = document.getElementsByClassName("vertical");
    let horz = document.getElementsByClassName("horizontal");

    if (this.addToggle) {
      this.renderer.addClass(vert[0], "verticalToggle");
      this.renderer.addClass(horz[0], "horizontalToggle");
    }
    else if (!this.addToggle) {
      this.renderer.removeClass(vert[0], "verticalToggle");
      this.renderer.removeClass(horz[0], "horizontalToggle");
    }

  }

  addFormToggle() {
    this.addFormToggleEvent.emit();
    this.addFormIconToggle();
  }
}

App.Component HTML

<div class="mainContainer">
    <div class="header">
        <menu-navigation (addFormToggleEvent)="childAddFormToggle()">
        </menu-navigation>
    </div>
    <div class="newProjectForm" *ngIf="toggleForm">
    <project-form (closeFormEvent)="childAddFormToggle()"></project-form>
    </div>
</div>

App.component TS

import { MenuNavigationComponent } from './menu-navigation/menu-navigation.component';

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


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'journal';
  toggleForm;

  public toggleProjectForm: Event;

  @Input() event: Event;
  @ViewChild(MenuNavigationComponent, { static: false }) child: MenuNavigationComponent;

  constructor() { }

  childAddFormToggle() {
    this.toggleForm = !this.toggleForm;
    this.child.addFormIconToggle();
  }
}

Solution

  • It is most likely triggering twice because you are calling it twice: Once in your childAddFormToggle() method, and once when dispatching the event in addFormToggle().

    But from your code I can't see the need to even do this. You could just rewrite it like this:

    Html for Navigation

    <div class="navButton add" (click)="toggleFormToggle()">
      <!-- Add the class in HTML conditionally, angular will take care of applying the -->
      <!-- class for you when 'addToggle' is set to true and removing it when set to false -->
    
      <div class="bar vertical" [class.verticalToggle]="addToggle"></div>
      <div class="bar horizontal" [class.horizontalToggle]="addToggle"></div>
    </div>
    

    TS for Navigation

    import { Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
    
    @Component({
      selector: 'menu-navigation',
      templateUrl: './menu-navigation.component.html',
      styleUrls: ['./menu-navigation.component.scss']
    })
    export class MenuNavigationComponent implements OnInit {
    
      // With this convention (@Input addToggle, @Output addToggleChange ) you can use two-way binding when using this component [(addToggle)]="someVariable"
      @Input() addToggle:boolean = false;
      @Output() addToggleChange = new EventEmitter<boolean>();
    
      constructor() { }
      ngOnInit() { }
    
      // Only set the value in the internal state and bubble up the event, angular handles the class setting for us in the template
      toggleFormToggle() {
        this.addToggle = !this.addToggle;
        this.addToggleChange.emit(this.addToggle);
      }
    }
    

    app.component html

    <div class="mainContainer">
        <div class="header">
            <!-- Just two-way bind the variable toggleForm so it will effective mirror the addToggle variable in menu-navigation -->
    
            <menu-navigation [(addToggle)]="toggleForm">
            </menu-navigation>
        </div>
        <div class="newProjectForm" *ngIf="toggleForm">
    
        <!-- Change the variable, the two-way binding above will reflect it back into the menu-navigation component -->
        <project-form (closeFormEvent)="toggleForm = !toggleForm"></project-form>
        </div>
    </div>
    

    app.component ts

    import { Component, Input, ViewChild } from '@angular/core';
    
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.scss']
    })
    export class AppComponent {
      title = 'journal';
      toggleForm:boolean = false;
    
      @Input() event: Event;
    
      constructor() { }
    
      // You don't actually need a method doing anything for you
    }
    

    Here a working Stackblitz showcasing my approach.