【问题标题】:ngOnChanges not firing when input property changed输入属性更改时不会触发 ngOnChanges
【发布时间】:2016-04-06 08:12:38
【问题描述】:

当改变 angular2 中的组件属性时,您能否以编程方式触发 Angular 的更改检测?

@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';
   }
}

【问题讨论】:

  • 要手动运行更改检测,请尝试使用 ApplicationRef.tick()。但是,我不相信 ngOnChanges() 会被调用,因为主机属性没有改变。为什么要更改作为输入属性的子组件中的子属性?
  • 上面的代码不是实际代码,我只是用它来演示问题。我的真实代码是一个单元测试,我已经使用TestComponentBuilder 获得了组件的一个实例。我希望单元测试代码修改message 属性的值,并检查ngOnChanges 中的代码是否发出了事件。但是经过几个小时的挠头后,我想不出任何方法来实现这一点。我也试过修改DebugElement上的nativeElement无效。
  • 我一直在寻找像ComponentFixture.modifyHostBinding(property: string, newValue: any) 这样的方法,或者类似的方法,但似乎没有类似的方法,所以我提出了一个更通用的问题。
  • 你得到答案了吗?
  • 您在这里要做的是创建一个虚拟主机组件,如TestHostComponent,它将按照规范绑定message 属性。然后在您的测试中使用 fixture.detectChanges() 检查对 message 属性的更改。见相关:stackoverflow.com/questions/37408801/…

标签: angular


【解决方案1】:

它不起作用的原因可以在源代码中找到。

https://github.com/angular/angular/blob/885f1af509eb7d9ee049349a2fe5565282fbfefb/packages/core/src/view/provider.ts

从哪里调用 ngOnChanges 以及在哪里构建 SimpleChanges 结构与组件/指令代码密切相关。

它不仅仅是一个运行的“更改跟踪器”,它会查看每个属性,无论它是如何设置的,因此 ngOnChanges 仅适用于由父组件设置的绑定。

这就是 ngDoCheck 的用武之地,可能还有 KeyValueDiffers。

另见:

https://netbasal.com/angular-the-ngstyle-directive-under-the-hood-2ed720fb9b61 https://juristr.com/blog/2016/04/angular2-change-detection/

【讨论】:

    【解决方案2】:

    尝试手动调用更改检测或花费大量时间解决此问题的方法太过分了,为什么不创建一个函数来处理所需的突变并在ngOnChangesdoSomething 中调用它呢?类似:

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

    所以viewMessage 将是您将使用和控制模板的属性。

    【讨论】:

      【解决方案3】:

      我遇到了同样的问题,这是我正在使用的一个简单但不是很优雅的解决方法。传入另一个属性强制触发ngOnChanges方法

      <div poll-stat-chart [barData]="barData" [changeTrigger]="changeTrigger"></div>
      

      在父组件类中,当你想手动触发子组件的ngOnChanges方法时,只需修改“changeTrigger”属性

      ParentComponent 类(poll-stat-chart 为子组件)

           @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 ++ ;           
              }
      
          }
      

      然后在子组件类中,添加一个属性[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');}
      
      }
      

      【讨论】:

      • 如此优雅的 hack。
      【解决方案4】:

      似乎无法修改this 上的输入绑定并在更改检测期间对其进行检测。但是,可以通过将整个组件包装在另一个组件中来修复我正在编写的单元测试

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

      然后我在MyHostComponent 上运行测试,而不是MyComponent

      我已经提交了一个issue to angular 请求将一个方法添加到ComponentFixture 以便这样的测试更容易编写。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-04-21
        • 1970-01-01
        • 2017-11-28
        • 2017-11-29
        • 2019-08-02
        • 2021-01-27
        • 1970-01-01
        相关资源
        最近更新 更多