Angular change detection does not run with onPush when using a Subject - javascript

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.

Related

Angular 10: Console shows my object as "unidentified" (pass data from parent to child)

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

How to make child component detects object (#Input()) from parent component has changed in Angular

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
}

Angular 2+: Child components ts variable changes but UI does not show changed value?

I have a child TestComponent component as follows:
import { Component, OnInit, Input } from '#angular/core';
import { ApiService } from '../../../api.service';
#Component({
selector: 'app-test',
templateUrl: './test.component.html'
})
export class TestComponent implements OnInit {
constructor(private apiService: ApiService) { }
testDisplayMessage = 'No data to show';
ngOnInit() {
}
getMessage(param: string) {
this.callingTest = true;
this.apiService.getTest( param ).subscribe( data => {
this.setTestDisplayMessage( data );
this.callingTest = false;
}, err => {
console.log( JSON.stringify( err ) );
this.setTestDisplayMessage( 'Failed to get data' );
this.callingTest = false;
} );
}
setTestDisplayMessage( message: string ) {
this.testDisplayMessage = message;
}
}
contents of test.component.html
<p style="padding: 10px;">{{ testDisplayMessage }}</p>
Use in parent componet :
Trigger JS Code in parent component on button click,
import { TestComponent } from './test/test.component';
....
.....
#Component({
providers: [ TestComponent ],
templateUrl: 'parent.component.html'
})
export class ParentComponent implements OnInit {
...
constructor(private testComponent: TestComponent) { }
...
// Button on parent template triggers this method
getMessage() {
this.testComponent.getMessage('Hello');
}
...
}
Html tag added in parent component,
<app-test></app-test>
When I debugged above code trigger point, call to setTestDisplayMessage() happens the field testDisplayMessage in TestComponent gets changed but UI shows the old message 'No data to show', why is the message on change does not reflect on UI template? Or this is not the way it is supposed to get used? Shall I use #Input
Update:
Based on the pointers given in the following answers as well as comment sections, I changed my component as #ViewChild so in above parent component instead of passing the child component as an argument to constructor I declared it as child component using #ViewChild, so code changes as follows,
Earlier wrong code
constructor(private testComponent: TestComponent) { }
Solution
#ViewChild(TestComponent)
testComponent: TestComponent;
I found this article useful.
Use #ViewChild()
In html file:
<app-test #childComp></app-test>
In parent component.ts file
import { Component, OnInit, ViewChild } from '#angular/core';
....
.....
#Component( {
templateUrl: 'parent.component.html'
} )
export class ParentComponent implements OnInit {
#viewChild('childComp') childComp: any;
constructor() { }
...
// Button on parent template triggers this method
getMessage() {
this.childComp.getMessage('Hello');
}
...
}
Update:
Based on the pointers given in the following answers as well as comment sections, I changed my component as #ViewChild so in above parent component instead of passing the child component as an argument to constructor I declared it as child component using #ViewChild, so code changes as follows,
Earlier wrong code
constructor(private testComponent: TestComponent) { }
Solution
#ViewChild(TestComponent)
testComponent: TestComponent;
I found this article useful.
definitely use #Input() but on set method
#Input()
set someProperty(value) {
// do some code
}
now every time you pass new value here, code will run
basically, your approach is wrong, please use Input() or Services to share data between components.
however, if you want to make ur code work, the below may work
import change detector
constructor(private cdRef: ChangeDetectorRef) {
}
note: import reference ->
import { ChangeDetectorRef } from '#angular/core';
execute detect change after the value is updated
setTestDisplayMessage( message: string ) {
this.testDisplayMessage = message;
this.cdRef.detectChanges();
}
I hope this helps

Update parent boolean from child component in Angular 5

I simply want to update a boolean value in my parent component on click of a button in my child component. I have a slide-out child component that I hide and show based on a dynamic ngClass. The class is set based on the boolean value from the parent. However when I close that component from a button in the child, I want to update the boolean value in the parent:
The parent component typescript:
export class AppComponent implements OnInit {
showFlyout: boolean
constructor() {}
ngOnInit() {
this.showFlyout = false;
}
}
And parent html:
<main>
<button (click)="showFlyout = !showFlyout"><i class="fa fa-check"></i>Show Flyout</button>
{{showFlyout}}
<app-child id="flyout" [ngClass]="showFlyout ? 'active' : ''"></app-child>
</main>
The child component typescript:
export class ActivateFlyoutComponent implements OnInit {
constructor() { }
ngOnInit() {
}
closeActivationFlyout() {
const flyout = document.getElementById('flyout');
flyout.classList.remove('active');
}
}
And child component html:
<button (click)="closeFlyout()">Close</button>
Here's a Stackblitz. You can see clicking the parent button properly toggles the class but how can I update that boolean from click in the child and therefore make the closeActivationFlyout() method unnecessary in the child component?
Use #Output() like the others have mentioned, but it needs to emit an event and you also need to check for the event being triggered in the parent html.
Here's a working StackBlitz
In the child component.
#Output() onCloseClick = new EventEmitter();
closeFlyout() {
this.onCloseClick.emit();
}
And in the parent components html.
<app-child id="flyout" [ngClass]="showFlyout ? 'active' : ''" (onCloseClick)="showFlyout = false"></app-child>
You can also create a function in the parent component, and trigger that like (onCloseClick)="doFunction()"
You can use two-way databinding to make it works:
AppComponent:
<app-child id="flyout" [(showFlyoutModel)]="showFlyout" [ngClass]="showFlyout ? 'active' : ''"></app-child>
ChildComponent :
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html'
})
export class ChildComponent implements OnInit {
#Input()
showFlyoutModel;
#Output()
showFlyoutModelChange = new EventEmitter<boolean>();
constructor() { }
ngOnInit() {
}
closeFlyout() {
this.showFlyoutModelChange.emit(!this.showFlyoutModel);
}
}
https://stackblitz.com/edit/angular-v95emc?file=app%2Fchild-component%2Fchild.component.ts

Angular 2 Parent afterViewInit fired before Child afterViewInit

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 .. ?

Categories

Resources