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
Related
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 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
I have a parent component which has two templates (Stackblitz):
the first contains a simple data-bound text
the second contains a child component which updates data model (from its constructor and ngOnInit in this demo)
Here is the parent component template:
<ng-template [ngIf]="true">
<div>
<b>Value from TemplateComponent: </b>{{ dataService.value | async }}
</div>
</ng-template>
<ng-template [ngIf]="true">
<template-component></template-component>
</ng-template>
Both parent and child components are ChangeDetectionStrategy.OnPush and this is a requirement.
The issue is that the data updates starting from ngOnInit (and later ones) of the child component don't get picked up by the change detection in the parent component. This causes the parent component in the demo to display:
Value from TemplateComponent: value from constructor
instead of
Value from TemplateComponent: value from ngOnInit
since the CD picks up an update on constructor stage only, but not in ngOnInit and later:
export class TemplateComponent implements OnInit {
public constructor(public readonly dataService: DataService) {
this.dataService.setValue("value from constructor");
}
public ngOnInit(): void {
// This update is NOT picked up by CD in parent component
this.dataService.setValue("value from ngOnInit");
}
}
Except this approach with markForCheck(), I've also tried a simple #Output event in the child, but it obviously doesn't work, since the child is created via ng-template and actually is not a direct child of the parent component.
Could someone give an idea of what is the cleanest and Angular's OnPush-friendly approach for passing data to the parent from ng-template created control?
P.S.: By
the cleanest and Angular's OnPush-friendly approach
, I mean something that keeps all parts (components and service loosely-coupled), for example, without involving setTimeout() or other dirty fixes.
You've grasped the idea of using observables, async pipes and OnPush strategy, but the problem is that you have to go a little deeper and understand how angular component lifecycle events work.
Let's see what happens (simplified version)
Parent component constructor()
Child component constructor()
Parent component onInit() - evaluate #Input templates and ngIf directives
Child component onInit() - trigger changes to some properties used in parent
Parent afterViewInit()
Once onInit in child component is called, the directives in parent component were already evaluated! From angular perspective content inside [ngIf] template is already what it is suppose to be and there's need to run change detection again or update anything.
You shouldn't change data that is used in parent component from onInit() in child component, otherwise you will surely break something.
If you wait until all components are fully instantiated and trigger change, then all views will be updated.
eg. in your child component:
setTimeout(()=>this.dataService.setValue("hello"),1000)
That also works, if you try to markForCheck in ngAfterViewInit in your parent component
ngAfterViewInit(): void {
this.changeDetector.markForCheck();
}
or if you won't use *ngIf the value will be re-evaluated.
<div><b>Value from regular div: </b>{{ dataService.value | async }}</div>
All of mentioned workarounds are not advisable, just don't change data from child component's onInit and you will be fine.
I am using angular 4. I am a bit new to it although i had used Angular JS.
I have a parent component which has an array of objects
people: Person[]; //array of Person model
And in the parent component the user modifies people array through its view
Now there is a child component(personComponent) in the parent components view that iterates through the people array like so
<person *ngFor="let person of people; let i = index" [person]="person">
The child component shows percentges of each person in the personComponent
#Input() person:Person; //person model
percent:number;
ngOnInit(){
this.percent = this.person.percent;
}
Now in the parent component, when a new person is added to the people array, each child component is supposed to have a new percent.
So i created a method in the parent component that calculates the percent of each person
setPercent(){
this.people.forEach((p)=>{
p.percent = Math.round((p.value/this.total_amount)*100) //total amount is a variable in the parent component
})
}
Now every time a new person is added the setPerson method is called...the problem is that the child component does not update its data and the percent of each person in the people array is not updated.
I have tried using ngOnChanges on the child components person variable but that did not update the child components data and hence the child components view did not change as well. What can i do pls.
In your Person component, you take the percentage value on initialization and no longer update it. Instead of doing that, you could use the person property, that has the always up to date value. So you should be able to use it as
{{person.percentage}}
in your template.
You can use the onChanges lifecycle hook.
// Child component
import { Component, OnChanges, SimpleChanges } from '#angular/core';
ngOnChanges(changes: SimpleChanges) {
if (changes['person']) {
console.log(changes['person'].currentValue);
}
}
On your child component use the following:
import { ChangeDetectionStrategy } from '#angular/core';
#Component({
// your selector and template
changeDetection: ChangeDetectionStrategy.OnPush
})
and in your parent component try the following:
import {ChangeDetectionRef} from '#angular/core';
and Inject it in the constructor:
constructor(private cd:ChangeDetectorRef){}
and in your method that update the percentages:
setPercent(){
this.people.forEach((p)=>{
p.percent = Math.round((p.value/this.total_amount)*100) //total amount is a
variable in the parent component
});
// TRY THIS
this.cd.detectChanges();
// OR THIS
this.cd.markForCheck();
}
This code will tell the change detection to check of changes on your component and it's children.
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()