简体   繁体   English

Angular @Input getter / setter和非原始值

[英]Angular @Input getter/setter and non-primitive values

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. 但是,即使可以看到绑定的输入属性更新,setter也只被调用一次。

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. 我已经了解到,当对象中的嵌套属性发生更改时,Angular onChange事件不会触发。 Hence the decision to use getters/setters instead. 因此决定使用getter / setter。 However, as seen by this question using getters/setters did not work either. 然而,正如这个问题所见,使用getter / setter也无效。 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. 我已经改变了我的子组件以订阅父组件订阅的相同Observable,从而直接从服务接收更新并绕过父组件。 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. 我已经对Angulars绑定和TypeScript getter / setter进行了大量研究,并且从各方面来看,它看起来像我的代码显示工作,但事实并非如此。

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. 目标:了解为什么使用带有getter / setter的@Input绑定到子组件中的父组件属性不能像非主要类型那样工作。 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。 StackBlitz Live Demo StackBlitz现场演示

mock-data.service.ts 模拟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.ts(父cmp)

app.component.html <child-cmp [targetNumber]="targetNumber"></child-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 儿童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;
  }
}

There are two parts to this: 这有两个部分:

  1. Recognizing that the @Input decorator is only updated during change detection , therefore the setter assigned to the bound data will only fire during change detection. 认识到@Input装饰器仅在更改检测期间更新 ,因此分配给绑定数据的setter仅在更改检测期间触发。 This fact is clearly stated in the first two comment lines in the Angular source code. 在Angular源代码的前两个注释行中明确说明了这一事实。

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

  1. From 1, it then follows that we need to understand how Angulars change detection applies to non-primatives . 从1开始,我们需要了解Angulars变化检测如何应用于非原始性

To help explain this I will use the following object ObjA: 为了帮助解释这一点,我将使用以下对象ObjA:

public ObjA = {
    'prop1': 'value1',
    'prop2': 'value2'
  }

Angulars change detection fires when the value of the data bound property changes . 当数据绑定属性的值发生更改时,Angulars会更改检测触发器 However, when the property being bound to is an object like ObjA , it is a reference of ObjA that gets bound to, not the object itself. 但是,当绑定的属性是ObjA类的对象时,它是ObjA的引用,而不是对象本身。 It is for this reason when a property value in ObjA changes ( a state change) Angulars change detection does not fire. 因此,当ObjA的属性值发生变化(状态变化)时,角度变化检测不会触发。 Angular is not aware of the state of ObjA , but rather the reference to ObjA . 角度是不知道的状态ObjA ,而是参考ObjA

Thank you to @JBNizet and @Jota.Toledo for providing me the information (in the above comments) I needed to understand this topic. 感谢@JBNizet和@ Jota.Toledo向我提供的信息(在上面的评论中)我需要理解这个主题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM