The Problem: I want be able to call a function each time a property in the object the child component is bound to changes. However, the setter is only called once, even though the bound input property can visibly be seen updating.
This all came to be from the need to have a child component bind to its parent components property that happens to be a complex object with deeply nested properties. I've learned that the Angular onChange event does not fire when a nested property in an object changes. Hence the decision to use getters/setters instead. However, as seen by this question using getters/setters did not work either. I've since changed my child component to subscribe to the same Observable that the parent component is subscribed to, thereby receiving the updates directly from the service and bypassing the parent component all together. I've done a lot of research on Angulars binding and TypeScript getters/setters and by all accounts it looks like my code show work, but it does not.
Goal: Understand why binding to a parent components property in a child component by using @Input with a getter/setter does not work as expected for non-primative types. Is there a fundamental concept I am missing or is there an implementation error in my code?
I will show some source code here and also attach a StackBlitz for anyone who wants to see it live in action. StackBlitz Live Demo
mock-data.service.ts
@Injectable()
export class MockDataService {
public updateSubject: Subject<any> = new Subject();
public numObj = {
'prop1': 'stuff',
'prop2': 'stuff',
'prop3': 'stuff',
'prop4': 'stuff',
'level1': {
'level2': {
'target': 0 //target is the prop that will be getting updated
}
}
}
constructor() {
this.startDemo();
}
private startDemo(): void {
//This is simulating the server sending updates
//to the numObj
setInterval(() => {
this.numObj.level1.level2.target += 1;
this.update();
}, 4000);
}
private update(): void {
try {
this.updateSubject.next(this.numObj);
} catch (err) {
this.updateSubject.error(err);
}
}
}
app.component.ts (parent cmp)
app.component.html
<child-cmp [targetNumber]="targetNumber"></child-cmp>
export class AppComponent implements OnInit {
public targetNumber: any;
public displayCurrentNumber: number;
constructor(private mockDataService: MockDataService){}
ngOnInit(){
this.mockDataService.updateSubject.subscribe({
next:(data) => this.onUpdate(data),
error: (error) => alert(error),
});
}
private onUpdate(data: any): void{
if(data){
this.targetNumber = data;
this.displayCurrentNumber = data.level1.level2.target;
}
}
}
child-cmp.component.ts
export class ChildCmpComponent {
private _targetNum: any;
public displayNumberObj: any;
public displayNumber: number;
public changeArray: string[] = [];
@Input()
set targetNumber(target: any){
this.changeArray.push('Setter(),');
this._targetNum = target;
this.setDisplay(this._targetNum);
}
get targetNumber(): any{
this.changeArray.push('Getter(),');
return this._targetNum;
}
private setDisplay(target: any): void{
this.changeArray.push('setDisplay(),');
this.displayNumberObj = target;
this.displayNumber = target.level1.level2.target;
}
}