Reputation: 2031
Can you programatically trigger angular's change detection when mutating a component property in angular2?
@Component({
selector: 'my-component',
})
class MyComponent implements OnChanges {
@Input() message: string;
ngOnChanges(changeRecord) {
for (var change in changeRecord) {
console.log('changed: ' + change);
}
}
doSomething() {
// I want ngOnChanges to be called some time after I set the
// message. Currently it is only called if the host element
// changes the value of [message] on the element.
this.message = 'some important stuff';
}
}
Upvotes: 11
Views: 29205
Reputation: 146218
The reason it doesn’t work can be found in the source code.
Where ngOnChanges is called from, and where the SimpleChanges structure is built are very much tied into the component / directive code.
It’s not just a ‘change tracker’ running that looks over every property however it was set, so ngOnChanges only works for bindings set by parent components.
This is where ngDoCheck comes in and possibly KeyValueDiffers.
See also:
https://netbasal.com/angular-the-ngstyle-directive-under-the-hood-2ed720fb9b61 https://juristr.com/blog/2016/04/angular2-change-detection/
Upvotes: 3
Reputation: 41
Trying to manually call change detection or spent a lot of time on a workaround for this is way overkilling, why not creating a function to handle the desired mutation and call it in both ngOnChanges
and doSomething
? something like:
@Component({
selector: 'my-component',
})
class MyComponent implements OnChanges {
@Input() message: string;
viewMessage: string;
ngOnChanges(changes: { [propertyName: string]: SimpleChange }) {
for (let propName in changes) {
if (propName === 'message') {
this.updateView(this.message);
}
}
}
doSomething() {
this.viewMessage = 'some important stuff';
}
updateView(message: string) {
this.viewMessage = message;
}
}
So viewMessage
will be the attribute you'll be using and controlling the template.
Upvotes: 4
Reputation: 7860
I was having the same issue, and this is a simple but not very elegant workaround I am using. Pass in another property to force trigger ngOnChanges method
<div poll-stat-chart [barData]="barData" [changeTrigger]="changeTrigger"></div>
In the parent component class, whenever you want to manually fire the ngOnChanges method on child component, just modify "changeTrigger" property
ParentComponent Class (poll-stat-chart is the child component)
@Component({
directives: [PollStatChartCmp],
template: `
<div poll-stat-chart [barData]="barData" [changeTrigger]="changeTrigger">
</div>
<button (click)="triggerChild()"></button>
`
}
export class ParentComponent {
changeTrigger = 1;
barData = [{key:1, value:'1'}, {key:2, value'2'}];
triggerChild() {
this.barData[0].value = 'changedValue';
//This will force fire ngOnChanges method of PollStatChartComponent
this.changeTrigger ++ ;
}
}
And then in child component class, add a property [changeTrigger]
@Component({
selector: '[poll-stat-chart]',
inputs: ['barData', 'changeTrigger'],
template: `
<h4>This should be a BAR CHAR</h4>
`
})
export class PollStatChartCmp {
barData;
changeTrigger;
constructor(private elementRef: ElementRef) {
this.render();
}
ngOnChanges(changes) {
console.log('ngOnChanges fired');
this.render();
}
render() { console.log('render fired');}
}
Upvotes: 5
Reputation: 2031
There seems to be no way to modify an input binding on this
and have it detected during change detection. However it was possible to fix the unit test I was writing by wrapping the whole component in another component
@ng.Component({
selector: 'my-host-component',
template: '<my-component [message]="message" (change)="change.emit($event)"></my-component>'
directives: [MyComponent]
})
class MyHostComponent {
message: string;
change = new EventEmitter<any>();
}
I then ran the test on MyHostComponent
, rather than MyComponent
.
I've submitted an issue to angular requesting that a method be added to ComponentFixture
so that tests like this are easier to write.
Upvotes: 1