Angular 2 eventEmitter dosen't work - javascript

I need to make something simple, I want to display a dialog when I click on an help icon.
I have a parent component:
#Component({
selector: 'app-quotation',
templateUrl: './quotation.component.html'
})
export class QuotationComponent implements OnInit {
public quotation: any = {};
public device: string;
public isDataAvailable = false;
#Output() showPopin: EventEmitter<string> = new EventEmitter<string>();
constructor(private quotationService: QuotationService,
private httpErrors: HttpErrorsService,
private popinService: PopinService) {}
moreInfo(content: string) {
console.log('here');
this.showPopin.emit('bla');
}
}
And his html:
<ul>
<li *ngFor="let item of quotation.HH_Summary_TariffPageDisplay[0].content">
<label></label>
<i class="quotation-popin" (click)="moreInfo()"></i>
<div class="separator"></div>
</li>
</ul>
My popin component:
#Component({
selector: 'app-popin',
templateUrl: './popin.component.html',
styleUrls: ['./popin.component.scss']
})
export class PopinComponent implements OnInit {
public popinTitle: string;
public popinContent: string;
public hidden: boolean = true;
constructor() { }
openPopin(event):void {
console.log("here");
this.hidden = false;
}
}
His HTML:
<div class="card-block popin-container" (showPopin)="openPopin($event)" *ngIf="!hidden">
<div class="card">
<div class="popin-title">
{{ popinTitle }}
<i class="icon icon-azf-cancel"></i>
</div>
<div class="popin-content">
{{ popinContent }}
</div>
</div>
</div>
My parent component is loaded in a router-outlet and my popin is loaded on the same level than the router-outlet, like this:
<app-nav-bar></app-nav-bar>
<app-breadcrumb></app-breadcrumb>
<div class="container">
<router-outlet></router-outlet>
</div>
<app-popin></app-popin>
My problem is the eventEmitter doesn't work and i don't know why, someone can explain me ?
thx,
regards

EventEmitters only work for direct Parent-Child component relationships. You do not have this relationship with the components you are describing here.
In a parent-child relatonship, we will see the child's component element within the parent's template. We do not see this in your example.
You have two options:
Refactor to use a parent-child relationship
Use a service for communication
If you go with option 2, the service should just contain an observable that one component calls next on, and the other component subscribes to.
#Injectable()
export class PopinService {
showPopin = new ReplaySubject<string>(1);
}
Inject this in QuotationComponent and modify moreInfo
moreInfo(content: string): void {
this.popinService.showPopin.next('bla' + content);
}
In PopinComponent inject the popinService and add the following:
ngOnInit() {
this.popinService.showPopin.subscribe(content => {
this.hidden = false;
this.popinContent = content;
});
}

It's because you misuse it.
In your popin component, you just call the function and do a log, and set a variable to false.
And nowhere I can see that you use the app-quotation selector, so you don't really use it, do you ?

Looks like you are sending an output to a child component (popin) . Ideally if you give output that means it should be from child to parent and from parent to child, it is Input.

Related

How can I receive output from another Angular component?

Watched angular tutorial, and just followed the steps but now I'm struggling with #Output. I can't receive the value from another component in my home component.
I tried to use Output with EventEmitter but somehow it's not working.
Home component
<mat-drawer-container [autosize]="true" class="min-h-full max-w-7xl mx-auto">
<mat-drawer mode="side" opened class="p-6">
<app-filters (showCategory)="onShowCategory($event)"></app-filters>
</mat-drawer>
<mat-drawer-content class="p-6"
><app-products-header (columnsCountChange)="onColumnsCountChange($event)">{{
category
}}</app-products-header></mat-drawer-content
>
</mat-drawer-container>
Home Component TS
import { Component } from '#angular/core';
#Component({
selector: 'app-home',
templateUrl: `home.component.html`,
})
export class HomeComponent {
cols = 3;
category: string | undefined;
onColumnsCountChange(colsNum: number): void {
this.cols = colsNum;
}
onShowCategory(newCategory: string): void {
this.category = newCategory;
}
}
<mat-expansion-panel *ngIf="categories">
<mat-expansion-panel-header>
<mat-panel-title>CATEGORIES</mat-panel-title>
</mat-expansion-panel-header>
<mat-selection-list [multiple]="false">
<mat-list-option *ngFor="let category of categories" [value]="category"
><button (click)="onShowCategory(category)">
{{ category }}
</button></mat-list-option
>
</mat-selection-list>
</mat-expansion-panel>
Category Filter TS
#Component({
selector: 'app-filters',
templateUrl: 'filters.component.html',
})
export class FiltersComponent {
#Output() showCategory = new EventEmitter<string>();
categories = ['shoes', 'sports'];
onShowCategory(category: string): void {
this.showCategory.emit(category);
}
}
Here you are sending data from parent component (Home Component) to a child component (Category Filter) so you should use #input decorator instead of #output. Check this article https://angular.io/guide/inputs-outputs.
#Component({
selector: 'app-filters',
templateUrl: 'filters.component.html',
})
export class FiltersComponent {
#Input() showCategory = new EventEmitter<string>();
categories = ['shoes', 'sports'];
onShowCategory(category: string): void {
this.showCategory.emit(category);
}
}
Maybe try
this.showCategory.next(category); //instead of emit.
I just realized I did this in my application with Angular 14. I know before it I used emit method so not sur if there is a difference but your code look good.
Other thing: you want to display in an other child (app-products-header) component so I think you need to use
<ng-template></ng-template>
in the html child
But you can pass the value through #Input to app-products-header too and display it with interpolation like any other variables

how to pass form value to another component in angular

I want to show taxDetailsId in my child component Html page.
But when click submit button.
After click submit button then shows taxDetailsId in my child component Html page.
Parent Component
export class OnlinePaymentComponent implements OnInit {
HttpClient: any;
paymentForm: FormGroup = this.formBuilder.group({
taxDetailsId: ['', [Validators.required]]
});
constructor(
private formBuilder: FormBuilder,
private router: Router,
) {}
ngOnInit() {}
submitForm(): void {
if (!this.paymentForm.valid) {
this.router.navigate(['/home/online-payment/error']);
return;
}
}
}
Parent.Component.html
<form [formGroup]="paymentForm" (ngSubmit)="submitForm()">
<label>Tax Details Id</label>
<input type="text" formControlName="taxDetailsId" placeholder="Tax Details Id" />
<button>Pay Bill</button>
<form>
Child Component
export class OnlinePaymentErrorComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
Child.Component.html
<div>
<button [routerLink]="['/home/online-payment']" >Back Home</button>
</div>
you can try this pattern this.router.navigate(['/heroes', { id: heroId }]);
https://angular.io/guide/router
you can use angular #Input() decorator for it.
Child Component
import { Component, Input } from '#angular/core';
export class ChileComponent {
#Input() public taxDetailsId: number;
}
Child Component HTML
enter code here
<div>
{{ taxDetailsId }}
<button [routerLink]="['/home/online-payment']" >Back Home</button>
</div>
Parent Component HTML
<app-child-component [taxDetailsId]="taxDetailsId"> </app-child-component>
https://angular.io/guide/inputs-outputs
You can pass components around using Angular's InjectionToken.
First you start off by creating the token:
export const ONLINE_PAYMENT_REF = new InjectionToken<OnlinePaymentComponent>('OnlinePaymentComponent');
Next you add the token to one of the root components as a provider, in this case it is the OnlinePaymentComponent. This way everything that is a child of this component, and everything that is a child of those components, and so on; will have a reference to the main parent that we create here:
#Component({
selector: 'online-payment',
template: `
<online-payment-error></online-payment-error>
`,
providers: [
{
provide: ONLINE_PAYMENT_REF,
// Forwards the instance of OnlinePaymentComponent when injected into
// the child components constructor.
useExisting: forwardRef(() => OnlinePaymentComponent)
}
]
})
export class OnlinePaymentComponent {
message = 'I am the Online Payment Component';
}
Now that we have the main component setup, we can access it through the constructor of anything that is a child of OnlinePaymentComponent (no matter how deep it is).
#Component({
selector: 'online-payment-error',
template: `
<h2>Child</h2>
<strong>Parent Message:</strong> {{parentMessage}}
`
})
export class OnlinePaymentErrorComponent implements OnInit {
parentMessage = '';
constructor(
#Inject(ONLINE_PAYMENT_REF) private parent: OnlinePaymentComponent
) {}
ngOnInit() {
this.parentMessage = this.parent.message;
}
}
When all is said and done, you will see the following:
The pros of this method are that you don't have to bind values to the elements in the template, and if those components have components that need to reference the parent you wouldn't have to bind to those either. Since components look up the hierarchy till they find the first instance of the provider that we are looking for they will find the one in OnlinePaymentComponent.
This becomes very helpful when components get deeper and deeper into the parent component (say 5 levels deep), that means every time you would have to pass a reference to the template element 5 times, and if it changes or gets deeper you would have to update all the templates.
With this method we no longer need to update templates to pass data from one component to another component, we just request it in our constructor as seen in OnlinePaymentErrorComponent.
There are two simple ways:
Using query params (without routerLink).
Using Observables.
Using query params, you can use the router.navigate and pass the params you need (Id) along with the route.
eg: this.route.navigate(['yourroute/route', { tId: variableWithId }])
Using Observable, when you click on the button, use the same router navigate without params and pass the required data to an observable. On successful routing to the next page, get the resolved data from the observable.

Angular 4 show popup onclick by other component

i'm struggling about this problem and can't figure out.
I simply need to show a popup div situated in the page clicking from a menu entry in my navbar.component.
I added a property "show" in my popup which prints the "show" class on my div using the ngClass (with if) directive. I can get this working if the action button is inside my popup component but i cannot print the show class clicking on another component. The property in the Object get updated but the class is not printed. I'm using angular 4 with ng-bootstrap. I tried both with services and with parent/child emit event.
This is is my situation:
app.component.html
<app-nav-bar></app-nav-bar>
<app-login></app-login>
<router-outlet></router-outlet>
<app-footer></app-footer>
navbar.component.html
...
<button class="dropdown-item" (click)="showPopup()">LOGIN</button>
...
navbar.component.ts
import {Component, EventEmitter, Input, OnInit, Output} from '#angular/core';
#Component({
moduleId: module.id,
selector: 'app-nav-bar',
templateUrl: 'navbar.component.html',
styleUrls: ['./navbar.component.css'],
})
export class NavbarComponent implements OnInit {
#Output() show = new EventEmitter<boolean>();
ngOnInit() {
}
showPopup() {
this.show.emit(true);
}
}
login.component.html
<div id="wrapper-login-popup" class="fade-from-top" [(class.show)]="show">
<div id="container-login-popup">
<div class="row">
<div class="col-sm-12 text-center">
<img id="popup-bomb" src="assets/images/bomb.png" alt="bomb"/>
<img id="popup-close" class="close-icon" src="assets/images/close.png" alt="close"
(click)="closePopup()"/>
</div>
</div>
</div>
</div>
login.component.ts
import {Component, Input, OnInit} from '#angular/core';
import {AuthService} from '../services/auth.service';
import {IUser} from './user';
#Component({
selector: 'app-login',
templateUrl: 'login.component.html',
styleUrls: ['login.css']
})
export class LoginComponent implements OnInit {
private username: string;
private password: string;
#Input() show: boolean = false;
constructor(private AuthService: AuthService) {
}
ngOnInit() {
}
login() {
...
}
showPopup() {
console.log(this); //Show is false
this.show = true;
console.log(this); //Show is true but does not trigger the show class
}
closePopup() {
this.show = false;
}
}
The issue here is that your nav-bar and login components are siblings and can't directly communicate with each other. You have show as an output of navbar and as an input of login, but you haven't connected the dots.
You need to update your app.component to connect them.
export class AppComponent implements OnInit {
show = false;
onShow() { this.show = true; }
}
and in the template:
<app-nav-bar (show)="onShow()"></app-nav-bar>
<app-login [(show)]="show"></app-login>
There's a lot of two way binding going on here which works for something simple liek this, but generally it's a bad idea as it leads to unmaintainable code. You should choose one owner of the show variable and force all changes to it through him. In this case the app component is the most logical owner, so I'd change the login component to emit an event that changes the show variable in app component adn remove all 2 way bindings, but in a bigger app, you may even want a separate service that manages hiding/showing pop ups. This eliminates the need for the sending a message up and down your component tree, you can inject the service where it's needed.
As another commenter mentioned, you also should be using ngClass for class manipulation like
[ngClass]="{'show':show}"
a service based solution would look like
import {Subject} from 'rxjs/Subject';
#Injectable()
export class PopUpService {
private showPopUpSource = new Subject();
showPopUp$ = this.showPopUpSource.asObservable();
showPopUp() { this.popUpSource.next(true); }
closePopUp() { this.popUpSource.next(false); }
}
Then you provide in app module or at app component level:
providers:[PopUpService]
make sure you don't re provide this later, as you only want one copy to exist so everyone shares it.
then inject into both components, and have them call the services close or show pop up methods.
then in the login component you bind to the popUp$ observable like
constructor(private popUpSvc:PopUpService){}
show$;
ngOnInit() { this.show$ = this.popUpSvc.showPopUp$; }
showPopUp() { this.popUpSvc.showPopUp(); }
closePopUp() { this.popUpSvc.closePopUp(); }
and in the template subscribe w async pipe like
<div id="wrapper-login-popup" class="fade-from-top" [ngClass]="{'show': (show$ | async) }">
The reason for using the async pipe is garbage collection managemetn is simpler. If you don't use async, you need to garbage collect manually in ngOnDestroy by calling unsubscribe(), otherwise your subscriptions will keep stacking up. There is also a more nuanced benefit in that the async pipe triggers change detection, but this only becomes important if you start using onPush change detection for performance optimization.

Add a component dynamically to a child element using a directive

Trying to place a component dynamically to a child element, using a directive.
The component (as template):
#Component({
selector: 'ps-tooltip',
template: `
<div class="ps-tooltip">
<div class="ps-tooltip-content">
<span>{{content}}</span>
</div>
</div>
`
})
export class TooltipComponent {
#Input()
content: string;
}
the directive:
import { TooltipComponent } from './tooltip.component';
#Directive({
selector: '[ps-tooltip]',
})
export class TooltipDirective implements AfterViewInit {
#Input('ps-tooltip') content: string;
private tooltip: ComponentRef<TooltipComponent>;
constructor(
private viewContainerRef: ViewContainerRef,
private resolver: ComponentFactoryResolver,
private elRef: ElementRef,
private renderer: Renderer
) { }
ngAfterViewInit() {
// add trigger class to el
this.renderer.setElementClass(this.elRef.nativeElement, 'ps-tooltip-trigger', true); // ok
// factory comp resolver
let factory = this.resolver.resolveComponentFactory(TooltipComponent);
// create component
this.tooltip = this.viewContainerRef.createComponent(factory);
console.log(this.tooltip);
// set content of the component
this.tooltip.instance.content = this.content as string;
}
}
The problem is that this is creating a sibling and I want a child (see bellow)
result:
<a class="ps-btn ps-tooltip-trigger" ng-reflect-content="the tooltip">
<span>Button</span>
</a>
<ps-tooltip>...</ps-tooltip>
wanted result:
<a class="ps-btn ps-tooltip-trigger" ng-reflect-content="the tooltip">
<span>Button</span>
<ps-tooltip>...</ps-tooltip>
</a>
Thanks in advance for your help!
Even dynamic component is inserted as sibling element you can still move element to desired place by using:
this.elRef.nativeElement.appendChild(this.tooltip.location.nativeElement);
Plunker Example
A better approach would be to have a nested ng-template with template reference variable on it such that the component is added as a sibling to ng-template but is now child to ng-template's parent.
Your template should be
<div class="ps-tooltip">
<div class="ps-tooltip-content">
<span>{{content}}</span>
<ng-template #addHere></ng-template>
</div>
</div>
And in your component
#ViewChild('addHere') addHere: ViewContainerRef;
ngAfterViewInit() {
...
this.tooltip = addHere.createComponent(factory)
...
}

Angular2 - two way databinding on a component variable / component class property?

In Angular2 (Beta 6) I have a component for a main menu.
<mainmenu></mainmenu>
I want to bind a boolean for wide or narrow. So I made it into this:
<mainmenu [(menuvisible)]="true"></mainmenu>
But what I want (I think) is to bind to a javascript class property (as I may have other things to bind but want to be tidy by using a single class in the component).
I get an error
EXCEPTION: Template parse errors: Invalid property name
'menumodel.visible' ("
][(menumodel.visible)]="menumodel.visible">
If I try the same with a single variable instead of a class I get:
Template parse errors: Parser Error: Unexpected token '='
However this (one way binding?) does seem to work (but I might want to trigger the menu to go wide/narrow from another component so felt this should be a two-way data bound property):
<menu [vis]="true"></menu>
This is a bit of my menu component:
#Component({
selector: 'menu',
templateUrl: './app/menu.html',
providers: [HTTP_PROVIDERS, ApplicationService],
directives: [ROUTER_DIRECTIVES, FORM_DIRECTIVES, NgClass, NgForm]
})
export class MenuComponent implements OnInit {
mainmenu: MainMenuVM;
constructor(private _applicationService: ApplicationService) {
this.mainmenu = new MainMenuVM();
}
// ...ngOnInit, various functions
}
Here is my MainMenu View Model class
export class MainMenuVM {
public visible: boolean;
constructor(
) { this.visible = true; }
}
I'm trying to create a menu which has icons and text, but can go narrow to just show icons. I will emit this event upwards to a parent component to alter the position of the container next to the menu. Triggering a content container to maximised will trigger the menu to go narrow - I am not saying this is the best way, but I would like to resolve this particular question before going deeper.
Please note: I am not databinding to an input control here - just databinding to a component so I can then modify the UI.
This is from the Angular cheatsheet
<my-cmp [(title)]="name">
Sets up two-way data binding. Equivalent to: <my-cmp [title]="name" (titleChange)="name=$event">
Thanks in advance!
UPDATE
Integrating the code from the accepted answer and adapting for my particular use case here the final working code:
app.html
...header html content
// This is what I started with
<!--<menu [menuvisible]="true" (menuvisibleChange)="menuvisible=$event"></menu>-->
// This is two way data binding
// 1. Banana-in-a-box is the input parameter
// 2. Banana-in-a-box is also the output parameter name (Angular appends it's usage with Change in code - to follow shortly)
// 3. Banana-in-a-box is the short hand way to declare the commented out code
// 4. First parameter (BIAB) refers to the child component, the second refers the variable it will store the result into.
// 5. If you just need an input use the remmed out code with just the first attribute / value
<menu [(menuvisible)]="menuvisible"></menu>
.. div content start
<router-outlet></router-outlet>
.. div content end
app.component.ts (root)
export class AppComponent implements OnInit{
menuvisible: Boolean;
}
menu.component.ts (child of root)
export class MenuComponent implements OnInit {
// Parameters - notice the appending of "Change"
#Input() menuvisible: boolean;
#Output() menuvisibleChange: EventEmitter<boolean> = new EventEmitter<boolean>();
// Init
ngOnInit() {
// Populate menu - fetch application list
this.getApplications();
// Initially we want to show/hide the menu depending on the input parameter
(this.menuvisible === true) ? this.showMenu() : this.hideMenu();
}
//...more code
}
menu.html
<div id="menu" [ngClass]="menuStateClass" style="position: absolute; top:0px; left: 0px;z-index: 800; height: 100%; color: #fff; background-color: #282d32">
<div style="margin-top: 35px; padding: 5px 0px 5px 0px;">
<ul class="menuList" style="overflow-x: hidden;">
<li>IsMenuVisible:{{menuvisible}}</li>
<li style="border-bottom: 1px solid #3d4247"><a (click)="toggleMenu()"><i class="fa fa-bars menuIcon" style="color: white; font-size: 16px;"></i></a></li>
<li *ngFor="#app of applications">
<a [routerLink]="[app.routerLink]">
<i class="menuIcon" [ngClass]="app.icon" [style.color]="app.iconColour" style="color: white;"></i>
<span [hidden]="menuStateTextHidden">{{ app.name }}</span>
</a>
</li>
</ul>
</div>
</div>
Remember to import what you need e.g.
import {Component, EventEmitter, OnInit, Input, Output} from
'angular2/core';
Highly recommend this video on You Tube:
Angular 2 Tutorial (2016) - Inputs and Outputs
For two-way binding you need something like:
#Component({
selector: 'menu',
template: `
<button (click)="menuvisible = !menuvisible; menuvisibleChange.emit(menuvisible)">toggle</button>
<!-- or
<button (click)="toggleVisible()">toggle</button> -->
`,
// HTTP_PROVIDERS should now be imports: [HttpModule] in #NgModule()
providers: [/*HTTP_PROVIDERS*/, ApplicationService],
// This should now be added to declarations and imports in #NgModule()
// imports: [RouterModule, CommonModule, FormsModule]
directives: [/*ROUTER_DIRECTIVES, FORM_DIRECTIVES, NgClass, NgForm*/]
})
export class MenuComponent implements OnInit {
#Input() menuvisible:boolean;
#Output() menuvisibleChange:EventEmitter<boolean> = new EventEmitter<boolean>();
// toggleVisible() {
// this.menuvisible = !this.menuvisible;
// this.menuvisibleChange.emit(this.menuvisible);
// }
}
And use it like
#Component({
selector: 'some-component',
template: `
<menu [(menuvisible)]="menuVisibleInParent"></menu>
<div>visible: {{menuVisibleInParent}}</div>
`
directives: [MenuComponent]
})
class SomeComponent {
menuVisibleInParent: boolean;
}
I've created a short plunkr.
ngModel Like Two-Way-Databinding for components
You have at least two possibilities to to create a two way databinding for components
V1: With ngModel Like Syntax, there you have to create a #Output property with the same name line the #Input property + "Change" at the end of the #Output property name
#Input() name : string;
#Output() nameChange = new EventEmitter<string>();
with V1 you can now bind to the Child Component with the ngModel Syntax
[(name)]="firstname"
V2. Just create one #Input and #Output property with the naming you prefer
#Input() age : string;
#Output() ageChanged = new EventEmitter<string>();
with V2 you have to create two attributes to get the two way databinding
[age]="alter" (ageChanged)="alter = $event"
Parent Component
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: `<p>V1 Parentvalue Name: "{{firstname}}"<br/><input [(ngModel)]="firstname" > <br/><br/>
V2 Parentvalue Age: "{{alter}}" <br/><input [(ngModel)]="alter"> <br/><br/>
<my-child [(name)]="firstname" [age]="alter" (ageChanged)="alter = $event"></my-child></p>`
})
export class AppComponent {
firstname = 'Angular';
alter = "18";
}
Child Component
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'my-child',
template: `<p>V1 Childvalue Name: "{{name}}"<br/><input [(ngModel)]="name" (keyup)="onNameChanged()"> <br/><br/>
<p>V2 Childvalue Age: "{{age}}"<br/><input [(ngModel)]="age" (keyup)="onAgeChanged()"> <br/></p>`
})
export class ChildComponent {
#Input() name : string;
#Output() nameChange = new EventEmitter<string>();
#Input() age : string;
#Output() ageChanged = new EventEmitter<string>();
public onNameChanged() {
this.nameChange.emit(this.name);
}
public onAgeChanged() {
this.ageChanged.emit(this.age);
}
}

Categories

Resources