0

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();
  }
}

1 Answers1

1

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.

pascalpuetz
  • 5,238
  • 1
  • 13
  • 26
  • Thank you very much for taking the time and effort into explaining this to me, this is wonderful and perfect. I'm having trouble shifting to thinking in angular and still doing things as I would in js. Thank you very much. – Upsetti_Spaghetti Jan 03 '20 at 23:26
  • 1
    You're very welcome :) More often than not, if you find the need to use @ViewChild or access methods from child components, you actually don't need that. That's pretty much the main purpose of angular - change your model and reflect it to html via bindings. If you come from a JQuery or similiar background this concept is difficult to really grasp at first, but well worth it in the long run :) – pascalpuetz Jan 04 '20 at 10:07