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