I have 2 components, one parent and one child, I would like to send the value of a variable to the parent component, I tried the code below but without succeed. The child component is sent the information correctly, but the parent is not receiving, looks like the function in the parent component is not identifying the receiving of the data to be triggered.
child component
...
private elements:any;
#Output() element = new EventEmitter<any>();
constructor() {
}
ngOnInit() {
this.sendContent();
}
sendContent() {
this.elements = "hi";
console.log("sended");
console.log(this.elements);
this.element.emit(this.elements);
//the function is activated and I can see the return in the console
}
parent component
...
constructor() {
}
ngOnInit() {
}
receiveContent(elements) {
console.log("received");
console.log(elements);
//the function is not activated when the child component is sent the data
}
Parent template
<app-child (sendContent)="receiveContent($event)"></app-child>
Thanks.
Inside parentheses you should put the name of the property that is decorated with #Output decorator. In this case (element)
Change your parent.html to this:
<app-child (element)="receiveContent($event)"></app-child>
In your parent component you need to bind to correct event from your child component.
In your child component you declared Output() called element so in this case the correct event name to use in the parent component will be element
Correct code will be:
<app-child (element)="receiveContent($event)"></app-child>
Official Angular Documentation - Component Interaction
Related
I have two components that communicate with each other, a regular component (parent) and a bootstrap modal component (child).
Both the parent and the child have a table with records. In the parent's table each record has a checkbox. When I select one checkbox or more and click on a button, an event is triggered that tries to populate the child's table.
The child's table gets populated and I have the ability to delete the records I wish from it. If I delete some records, close the modal (close the child) and decide to send the same data from the parent to the child again, the Input event is not triggered. However, if I choose to check different records in the parent and pass them to the child, the Input event is triggered correctly.
Here's what I have:
ParentComponent.component.html
<child-component #createNewProductRequestModal [data]="newProductRequest"></m-child-component>
ParentComponent.component.ts
private onNewProductRequest(toSend){
this.newProductRequest = toSend;
$('#createNewProductRequestModal').appendTo("body").modal('toggle');
}
ChildComponent.component.ts
#Input('data')
set data(data: any) {
if (data !== undefined || data !== "") {
this.products = data;
console.log("data");
}
}
constructor() { }
ngOnInit() { }
To test if the data is changed before I render the child's table, I log the data passed by the parent to the console. With this code, every time I execute the parent's onNewProductRequest(toSend) without changing the toSend variable, the child's modal renders but doesn't execute the Input event therefore not changing the data.
When you send the same data to the child component a second time, Angular does not register this as a change to the #Input(), as you are passing the same Object reference that you passed the first time, and Angular is just comparing the references.
Try this small change:
private onNewProductRequest(toSend){
this.newProductRequest = { ...toSend };
$('#createNewProductRequestModal').appendTo("body").modal('toggle');
}
This will mean that you pass a shallow copy of the data Object to the child, rather than just a modified version of the original Object. As this shallow copy will have a different reference to the original Object, Angular will pick it up as a change and trigger your #Input setter.
When you assign the data to the input, the first time detect the changes because the object changes, but then if yo change properties inside the object the reference and the object still be the same, so the changes doesn't fire. The solution is clone the object that you're sending via input to the child. Try changing this line:
this.newProductRequest = toSend;
For this:
this.newProductRequest = {...toSend};
You can use ngOnChanges
Definition from angular docs
A callback method that is invoked immediately after the default change detector has checked data-bound properties if at least one has changed, and before the view and content children are checked.
You have some Input on child element
#Input() nameOfInput: any;
Your child component must implement OnChanges like this:
export class ChildComponent implements OnChanges {
And in next step you want do something on every change of inputs.
ngOnChanges(changes: SimpleChanges) {
console.log(changes);
if ('nameOfInput' in changes) {
console.log('Old value: ', changes.nameOfInput.previousValue);
console.log('New value: ', changes.nameOfInput.currentValue);
}
}
Look at this example.
Example
try
in child component
import {Component, OnChanges, SimpleChanges, Input} from '#angular/core';
ngOnChanges(changes: SimpleChanges) {
for (let propName in changes) {
let change = changes[propName];
let curVal = JSON.stringify(change.currentValue);
let prevVal = JSON.stringify(change.previousValue);
console.log(curVal);
console.log(prevVal);
}
}
service.ts
clicked: string = '';
clickEvent(item){
this.clicked = ( this.clicked.length > 0 && this.clicked == item ) ? '' : item;
}
parent.component
constructor(private qqs: qqService) {}
clicked = this.qqs.clicked;
parent.html
<app-child [clicked]="clicked"></app-ou>
child.component
constructor(private qqs: qqService) {}
#Input() clicked;
click(item){
this.qqs.clickEvent(item);
}
child.html
<div class="a" (click)="click('some string')"></div>
If user click at child.html
child.component click() will work and pass some string to service
and service will change variable clicked
than parent. component get a new variable from service
and pass to all child by #Input()
Now my problem is a parent. component can't get a new variable from service
How to fix that?
With reference to Angular docs for component interacion
The child component exposes an EventEmitter property with which it emits events when something happens.
The parent binds to that event property and reacts to those events.
The child's EventEmitter property is an output property, typically adorned with an #Output decoration as seen in this ParentComponent
Here are the codes:
parent.component
constructor(private qqs: qqService) {}
onClicked(item) {
console.log(item)
}
parent.html
<app-child [clicked]="clicked" (onclicked)="onClicked($event)"></app-ou>
child.component
import { Component, EventEmitter, Input, Output } from '#angular/core';
constructor(private qqs: qqService) {}
#Input() clicked;
#Output() onclicked = new EventEmitter<any>();
click(item){
this.clicked.emit(agreed);
}
child.html
<div class="a" (click)="click('some string')"></div>
Problem with your code are following:
Service: whenever clickEvent it changes reference of clicked.
parent.component: you have saved reference of qqs.clicked in clicked(assuming clicked in your code refers to this.clicked), but whenever next time clickEvent will be called, the reference of qqs.clicked will be changed and inside parent clicked will have old reference.
Solution is either expose whole qqs to template by making it public in parent.component and in parent.html replace [clicked]="clicked" to [clicked]="qqs.clicked".
This will fix you problem, but i noted few more things:
In your parent.html <app-child [clicked]="clicked"></app-ou> starting tag is not matching end tag.
when you are already injecting Service in child too. what is need to pass value of clicked using property biding.
I have a scenario where a component injected through entryComponents. In this case how Parent component can listen child component event(i.e. Emitted through eventemitter)
If you injected it, that means you instantiated it yourself, then you can simply keep a reference on it and subscribe to the public EventEmitter.
After creating your ComponentPortal you attached it to a PortalOutlet or an OverlayRef. The return value of attach contains the instance of the injected component.
For example, let's say your component is named SomeComponent and has an EventEmitter named someEventEmitter:
let portal = new ComponentPortal(componentFactory.componentType);
let component = overlayRef.attach<SomeComponent>(portal);
component.instance.someEventEmitter.subscribe(() => {
//Some code
});
I have a parent and child component where the parent is passing an object to the child through the #Input decorator. The problem is the child gets the parent data only once, then after future changes to parent property that is passed to the child, the value is not being update.
On parent component change, do change in child component too
Here is how you can do with OnChanges interface which detects changes in the component
In parent.component.html
<app-child [child_name]="name"></app-child>
In child.component.ts
export class ChildComponent implements OnChanges {
#Input('child_name') name: string
text: string
constructor() { }
// on every change of #input 'name', this method will be triggered
ngOnChanges() {
this.text = 'Hi ' + this.name
}
}
DEMO
The data will be updated when whole reference will be updated. If you just update some properties inside that object, it will not be triggered. You need to change the reference of the passed object.
Example
<child [data]="myData"></child>
If you will update myData.name = "Test", it will not be triggered. You need to do something
this.myData = changedData;
A workaround can be using DoCheck lifecycle hook and try to check the property change manually. But generally it is more handy to change the reference.
if you want to get update value from parent compoent than you need to add changeDetection: ChangeDetectionStrategy.OnPush in your child component
Note: OnPush works by comparing references of the inputs of the component
#Component({
selector: 'abc',
templateUrl: 'component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class childComponent {
//below get updated value every time value changes in parent
#Input() inputFromParent: string;
}
in parent , use child component abc
<abc [inputFromParent]="passdata"></abc>
in parent ts
this.inputFromParent = newData;//done after first init
now if you change value of passdata then it will change value in childcomponent property too.
Ref : Understanding Change Detection Strategy in Angular
your parent comp html looks like :
<div>
<span (click)="expandAllClient = true "> EXPAND </span>
<span (click)="expandAllClient = false"> COLLAPSE </span>
</div>
<div>
<child-comp [expandAll]="expandAllClient"(flagChanged)="flagChanged($event)"></child-comp>
</div>
and dont forget to add :
#Input() expandAll: boolean = false; in your ts file
and in child comp you can directly use expandall(here) in all your operation like:
<div [ngClass]="{'openPanel': expandAll}></div>
here openpanel class will get applied if you have clicked EXPAND span in parent component
Try to get the changes by ngOnChanges
use it in the child component
ngOnChanges(changes: import("#angular/core").SimpleChanges): void {
}
you should read about change detection.
if you want to change data from parent component and others use service or ngrx-store also you can use redux
In angular docs there is a topic about listening for child events from parents. That's fine. But my purpose is something reverse!. In my app there is an 'admin.component' that holds the layout view of admin page (sidebar menu,task bar, status etc..).
In this parent component I configured router system for changing the main view between other pages of administrator.
The problem is for saving things after change, the user clicks on save button in task bar (that is placed in admin.component) and the child component must listen to that click event for doing save staff.
For the sake of posterity, just thought I'd mention the more conventional solution to this: Simply obtain a reference to the ViewChild then call one of its methods directly.
#Component({
selector: 'app-child'
})
export class ChildComponent {
notifyMe() {
console.log('Event Fired');
}
}
#Component({
selector: 'app-parent',
template: `<app-child #child></app-child>`
})
export class ParentComponent {
#ViewChild('child')
private child: ChildComponent;
ngOnInit() {
this.child.notifyMe();
}
}
I think that this doc could be helpful to you:
https://angular.io/docs/ts/latest/cookbook/component-communication.html
In fact you could leverage an observable / subject that the parent provides to its children. Something like that:
#Component({
(...)
template: `
<child [parentSubject]="parentSubject"></child>
`,
directives: [ ChildComponent ]
})
export class ParentComponent {
parentSubject:Subject<any> = new Subject();
notifyChildren() {
this.parentSubject.next('some value');
}
}
The child component can simply subscribe on this subject:
#Component({
(...)
})
export class ChildComponent {
#Input()
parentSubject:Subject<any>;
ngOnInit() {
this.parentSubject.subscribe(event => {
// called when the notifyChildren method is
// called in the parent component
});
}
ngOnDestroy() {
// needed if child gets re-created (eg on some model changes)
// note that subsequent subscriptions on the same subject will fail
// so the parent has to re-create parentSubject on changes
this.parentSubject.unsubscribe();
}
}
Otherwise, you could leverage a shared service containing such a subject in a similar way...
A more bare bones approach might be possible here if I understand the question correctly. Assumptions --
OP has a save button in the parent component
The data that needs to be saved is in the child components
All other data that the child component might need can be accessed from services
In the parent component
<button type="button" (click)="prop1=!prop1">Save Button</button>
<app-child-component [setProp]='prop1'></app-child-component>
And in the child ..
prop1:boolean;
#Input()
set setProp(p: boolean) {
// -- perform save function here
}
This simply sends the button click to the child component. From there the child component can save the data independently.
EDIT: if data from the parent template also needs to be passed along with the button click, that is also possible with this approach. Let me know if that is the case and I will update the code samples.
For those who are getting Cannot read property 'notifyMe' of undefined
Try calling the method inside ngAfterViewInit() intead of ngOnInit()