Angular @Input getter/setter和非原始值

Nar*_*arm 6 typescript angular-components angular

问题:我想每次绑定子组件的对象中的属性发生变化时都能调用一个函数.但是,即使可以看到绑定的输入属性更新,setter也只被调用一次.

这一切都来自于需要将子组件绑定到其父组件属性,该属性恰好是具有深层嵌套属性的复杂对象.我已经了解到,当对象中的嵌套属性发生更改时,Angular onChange事件不会触发.因此决定使用getter/setter.然而,正如这个问题所见,使用getter/setter也无效.我已经改变了我的子组件以订阅父组件订阅的相同Observable,从而直接从服务接收更新并绕过父组件.我已经对Angulars绑定和TypeScript getter/setter进行了大量研究,并且从各方面来看,它看起来像我的代码显示工作,但事实并非如此.

目标:了解为什么使用带有getter/setter的@Input绑定到子组件中的父组件属性不能像非主要类型那样工作.是否存在我缺少的基本概念或我的代码中是否存在实现错误?

我将在这里展示一些源代码,并为任何希望看到它实时运行的人附加StackBlitz. StackBlitz现场演示

模拟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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

app.component.ts(父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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

儿童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;
  }
}
Run Code Online (Sandbox Code Playgroud)

Nar*_*arm 6

这有两个部分:

  1. 认识到@Input 装饰器仅在更改检测期间更新,因此分配给绑定数据的 setter 只会在更改检测期间触发。这个事实在 Angular 源代码的前两行注释中清楚地说明了。

export interface InputDecorator { /** * Declares a data-bound input property. * * Angular automatically updates data-bound properties during change detection. *

  1. 从 1 开始,接下来我们需要了解 Angulars 变化检测如何应用于非原始类型

为了帮助解释这一点,我将使用以下对象 ObjA:

public ObjA = {
    'prop1': 'value1',
    'prop2': 'value2'
  }
Run Code Online (Sandbox Code Playgroud)

当数据绑定属性的值发生变化时,会触发 Angulars 更改检测。但是,当绑定到的属性是像 一样的对象时ObjA,它是ObjA绑定到的引用,而不是对象本身。正是由于这个原因,当属性值ObjA发生变化(状态变化)时,Angulars 变化检测不会触发。Angular 不知道 的状态ObjA,而是知道ObjA.

感谢@JBNizet 和@Jota.Toledo 为我提供了理解该主题所需的信息(在上述评论中)。