I have a parent container and a child component.
Child components are variable in number and are fetched off XHR request.
Parent Component:
#Component({
selector: 'parent',
template: `
<child *ngFor="let c of children" [c]="c"></child>
`
})
export default class ParentContainer implements AfterViewInit {
children: C[];
constructor(public api: Api) {
this.api.getC().subscribe(res => this.children = res);
ngAfterViewInit() {
console.log('parent afterNgView');
}
Child Component:
#Component({
selector: 'child',
template: `
<div>Child</div>
`
})
export default class ChildComponent implements AfterViewInit {
#Input() c: C;
ngAfterViewInit() {
console.log('child afterViewInit');
}
When I execute this I see parent afterNgView appear before all the child afterNgView logs. I was expecting the children ngAfterViewInit to execute first.
There must be a way to ensure all children are done loading before a parent handler is invoked. I looked through NG2 LifeCycle Hooks assuming the parent AfterViewInit would only get called after the children. That's not the case.
How can I get the children to inform the parent they are finished? There should be something out the box ... ?
This is a screenshot from NG2 LifeCycle Hooks guide (https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html)
This is what I'm doing until I find something cleaner:
Parent Component:
#Component({
selector: 'parent',
template: `
<child *ngFor="let c of children" [c]="c" (childIsLoaded)="childIsLoaded($event)"></child>
`
})
export default class ParentContainer implements AfterViewInit {
children: C[];
constructor(public api: Api) {
this.api.getC().subscribe(res => this.children = res);
childIsLoaded() {
console.log('Child\'s ngAfterViewInit Complete !!');
}
ngAfterViewInit() {
console.log('parent afterNgView');
}
Child Component:
#Component({
selector: 'child',
template: `
<div>Child</div>
`
})
export default class ChildComponent implements AfterViewInit {
#Input() c: C;
#Output() childIsLoaded = new EventEmitter<any>();
ngAfterViewInit() {
...init code...
this.childIsLoaded.emit();
}
In the above snippet, the child emits an event notifying the parent that its ngAfterViewInit fired. There must be something out of the box that does this for me ? Instead of me re-writing this child-notify-parent scheme for all my nested components .. ?
Related
I tried to use instance of parent component in child component via constructor. In other words, I create instance of parent component class as private property and use its properties, methods etc.
Besides that, I can affect to values of parent component properties directly without using Input, Output decorators, event listeners etc.
Parent
#Component({
selector: 'parent-component',
templateUrl: './parent-component.component.html',
styleUrls: ['./parent-component.component.scss']
})
export class ParentComponent implements OnInit {
someParentProperty: number = 10;
constructor() {}
ngOnInit() {}
someParentMethod = (num) => num**2;
}
Child
import { ParentComponent } from '../parent-component';
#Component({
selector: 'child-component',
templateUrl: './child-component.component.html',
styleUrls: ['./child-component.component.scss']
})
export class ChildComponent implements OnInit {
someChildProperty: number;
constructor(pc: ParentComponent) {}
ngOnInit() {
this.someChildProperty = this.pc.someParentMethod(this.pc.someParentProperty);
}
}
That's rather comfortable, but I'm not sure, that it's a best practice and right approach.
Could someone explain minuses of this one?
Why don't you use a service?
So that you can access your required method from both child and parent components.
E.g: A common service:
#Injectable({
providedIn: 'root',
})
export class CommonService {
someParentMethod(num) {
return num**2;
}
}
At ParentComponent:
export class ParentComponent implements OnInit {
constructor(private commonService: CommonService) {}
ngOnInit() {
console.log(commonService.someParentMethod(2));
}
}
You can do the same at ChildComponent you can do the same.
It's the best way for sharing.
You can find detail about services here.
I am trying to pass data from the parent to the child. I get the correct output when I pass the string (data1) from parent to child but when I try to display object (data) the console says it's unidentified and nothing is displayed on the screen.
Parent Component:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
data={
name:'Charles',
age:24,
email:'charles#gmail.com'
};
data1 = "Charles";
}
<app-contact [sendName] ="data"></app-contact>
Child Component
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.css']
})
export class ContactComponent implements OnInit {
#Input() sendName;
constructor() { }
ngOnInit(): void {
console.log(this.sendName);
}
}
<h1 class="text-center">Contact Form</h1>
<h3 class="pb-5">from the parent {{sendName.name}}</h3>
<h3 class="pb-5">from the parent {{sendName.age}}</h3>
please someone help me.
Thank you
If your data (in parent component) variable has static value then your child component will be
#Input() sendName;
ngOnInit(): void {
console.log(this.sendName);
}
If your data (in parent component) variable will assign from dynamic value then your child component will be
(Assume you have an API to call and update sendName variable on API response, but our child component is initialised without waiting the API response then we need to trigger #Input() to send updated data in child component )
#Input() set sendName(value: any) {
if(value){
console.log(value);
this.name = value
// call any function from here
}
}
name:any; // use this variable at anywhere
constructor(){
}
you might be rendering the child, before setting the value in data in parent
in Child component import Onchanges and get from ngOnchanges()
import { Component, OnInit, Input ,Onchanges} from '#angular/core';
export class ContactComponent implements OnInit,Onchanges {
#Input() sendName; constructor() { }
ngOnChanges() {
console.log(this.sendName); }
I have an object from parent component also received in a child component similar to this:
{
attribute: 'aaaa',
attribute2: [
{
value
},
{
value
},
{
value
},
]
}
This object is an #Input from a parent component. When I make changes to the objects inside the attribute2 array, I would like the child component detect that changes were made and then gets updated. As this is an object, I could'nt make it work, so I clone the entire object (this.objet = _.cloneDeep(this.object) in the parent component so then the child component detects that changes happened.
Is there any other way of doing this that does not clone the entire object? Thanks in advance
EDIT:
Child Component
export class ChildComponent implements OnInit, OnChanges {
#Input() public object: any;
}
html
<div>
<span>{{object.attribute}}</span>
<div *ngFor="let items of object.attribute2">{{item.value}}</div>
</div>
Parent Component
export class ParentComponent implements OnInit {
public object: any;
updateObject() {
this.object.attribute2[1] = 'Changed value';
this.object = _.cloneDeep(this.object);
}
}
html
<div>
<child-component [object]="object"></child-component>
</div>
An efficient way is to use EventEmitter and service communication to
trigger changes in the child component.
On way as mentioned by #Tony is to use ngOnChanges(). It is a good shortcut for detecting bounded properties change but as you add more and more bindings, using this hook will affect you application in the long run because it will run every time any of the bound property changes whether or not you desire it all the calls.
So for Service based communication, I've created an example on
Stackblitz:
https://stackblitz.com/edit/angular-fgut7t
Gist: https://gist.github.com/stupidly-logical/a34e272156b498513505127967aec851
In this example, I am binding an Array to the child component using #Input() an on addition of new data, the array is updated by the parent and the latest value is passed on the service which then emits this value. The child component subscribes to this value and the relevant code is executed.
The Service:
import { Injectable, EventEmitter } from '#angular/core';
#Injectable({
providedIn: "root"
})
export class DataService {
dataUpdated:EventEmitter<any> = new EventEmitter();
constructor() { }
setLatestData(data) {
this.dataUpdated.emit(data);
}
}
Child Component TS
import { Component, OnInit, Input } from '#angular/core';
import { DataService } from '../data-service.service';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() allData: [];
latestData: any;
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.dataUpdated.subscribe((data) => {
this.latestData = data;
});
}
}
Child Component HTML
<p>
Latest Data: {{ latestData }}
</p>
<h3>List:</h3>
<li *ngFor="let data of allData">
{{ data }}
</li>
Parent Component TS
import { Component } from '#angular/core';
import { DataService } from './data-service.service'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
dataArr = [];
constructor(private dataService: DataService){}
onAddTimestamp() {
let timestamp = new Date();
this.dataArr.push(timestamp);
this.dataService.setLatestData(timestamp);
}
}
Parent Component HTML
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<button
(click)="onAddTimestamp()"
>
Add Timestamp
</button>
<app-child
[allData] = "dataArr"
></app-child>
Use the ngOnChanges() lifecycle method in your component.
ngOnChanges is called right after the data-bound properties have been
checked and before view and content children are checked if at least
one of them has changed.
Some like this
#Input() object: string;
ngOnChanges(changes: SimpleChanges) {
console.log(changes.object.currentValue);
// You can also use object.previousValue and
// object.firstChange for comparing old and new values
}
I have 2 components
Parent component is
#Component({
selector: 'parent',
template: `
<child [obj]="obj"> </child>
`,
styleUrls: [''],
})
export class parentComponent implements OnInit{
obj = {
id:1;
name:'abc'
}
}
and child component is
#Component({
selector: 'child',
templateUrl: '',
styleUrls: [''],
})
export class ChildComponetimplements OnInit{
#Input() obj : any;
}
If I change any of the property in the obj in the parent, it is not getting updated in the child component.
Maybe because the obj reference is not changed.
Please suggest me the solution for this.
You have to use ngOnChanges like below
Parent Component
export class AppComponent {
obj = {
id:1,
name:'abc'
}
name = 'Angular';
changeObject() {
this.obj.id++;
}
}
Parent Template
<button (click)="changeObject()">Change Object</button>
<hello name="{{ name }}" [obj]="obj"></hello>
Child Component
import { Component, Input, OnInit, OnChanges } from '#angular/core';
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1><p>{{ obj | json }}</p>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit, OnChanges {
#Input() name: string;
#Input() obj;
ngOnInit() {
}
ngOnChanges(changes) {
this.obj = changes.currentValue ? changes.currentValue.obj : this.obj;
}
}
Working example is here in Stackblitz
Stackblitz Demo
obj = {
id: 1,
name: 'Hello'
}
changeObject() {
this.obj.name = 'Hello change';
}
Use ngOnChanges to listen to changes of input properties. You will pass the object from parent to child and whenever there is any change in the object that you have passed as input to the child will give SimpleChanges object in ngOnChanges hook of child component.
interface OnChanges {
ngOnChanges(changes: SimpleChanges): void
}
Example
#Component({selector: 'my-cmp', template: `...`})
class MyComponent implements OnChanges {
// TODO(issue/24571): remove '!'.
#Input()
prop !: number;
ngOnChanges(changes: SimpleChanges) {
// changes.prop contains the old and the new value...
}
}
For more on ngOnChanges
I have a parent component
#Component({
selector: 'mve-trace-multi-filter',
template: `
<child [itemsReady]="itemsReady$ | async"></child>
`
})
export class ParentComponent {
itemsReady$ : Subject<any> = new Subject();
ngOnInit() {
this.store.select('state').subscribe(data => this.itemsReady$.next(data));
}
}
And a child component with onPush
changeDetection: ChangeDetectionStrategy.OnPush
export class ChildComponent {
#Input() set itemsReady( items ) {
console.log('change', items);
}
}
}
The problem is that when the next() method is run, there is not change detection in the child component. I am excepting the child component to be updated because of the async pipe. What is wrong?
I saw the log as null in the first time but that's it.