使用OnPush策略对子元素进行ChangeDetection

时间:2019-07-03 08:35:59

标签: angular validation rxjs angular-forms

我有一个InputComponent,它用作多个本机输入的包装,并添加了一些逻辑。

此组件具有输入control,该输入接受FormControl并将其通过[formControl]绑定到输入字段。

为了提高性能,我将changeDetectionStrategy设置为OnPush。

现在我注意到,当我使用formGroup.get(...).setValidators(...)时,InputComponent不会更新其状态。我该如何执行呢?我看不到验证器的任何挂钩都会改变周期。

作为一种解决方法,我添加了一个公共API来手动调用detectChanges()

在验证规则有所更改且不会导致调用堆栈大小超出限制的情况下,我是否可以监听并调用detectChanges()


这可以解释为使用OnPush检测策略时如何调用整个树的更改检测


小型example在这里您可以看到一个复选框。单击按钮,看到没有区别。但是,如果单击复选框,它将显示一个星号。

有趣的是,当将requiredTrue分配给控件并且该控件没有true作为值时,表单仍然有效。我不明白为什么。

1 个答案:

答案 0 :(得分:0)

这里的实际问题是将验证器设置在外部会破坏不变性的概念。您拥有的输入是一个对象,仅该对象内部的属性被更改。 Angular不会将其注册为更改。

所以有趣的是,这不仅仅是ChangeDetction.OnPush的问题。使用默认的更改检测,由于会检查所有组件,因此angular会更新视图,但是如果您要注册到onChanges生命周期钩子,则会注意到实际更改未在任何一个更改检测配置中的生命周期钩子中注册。因此请注意,它似乎只能与默认更改检测一起使用,但不能完全起作用。因此,请注意与所使用的更改检测无关的对象可变性。


话虽如此:在您的情况下,在SoC之后,我实际上将设置验证器设置在输入组件内部,具体取决于布尔输入属性,而不是从外部设置验证器。在SoC旁边,这还具有以下优势:万一您的输入组件可能需要其他验证器,您实际上将所有验证器放在一个位置,并且一旦我只想删除所需的验证器,就可以重置那些验证器,因为据我所知目前尚无法删除特定的验证器。

您的input.component.ts看起来像这样(请注意,这只是伪代码):

input.component.ts

export class InputComponent implements OnChanges {
  @Input() required: boolean;
  @Input() control: FormControl;

  ngOnChanges(simpleChanges: SimpleChanges){
    if(simpleChanges.required) {
      if(simpleChanges.required.nextValue) {
        this.control.addValidator(...)
      } else {
        this.control.clearValidators()
      }
    }
  }

}

如果出于任何原因根本无法在输入中设置验证器,则实际上需要手动触发changeDetection。为此,我将执行以下操作:

app.component.ts

constructor(private fb: FormBuilder, private ref: ChangeDetectorRef) {}

  toggleValidator() {
    if (this.requiredTrue) {
      this.requiredTrue = false;
      this.formGroup.get('agreed').clearValidators();
      // this is important
      this.formGroup.get('agreed').updateValueAndValidity();
    } else {
      this.requiredTrue = true;
      this.formGroup.get('agreed').setValidators(Validators.requiredTrue)
      // this is important
      this.formGroup.get('agreed').updateValueAndValidity();
    }

    console.log(this.formGroup);
  }

input.component.ts

constructor(private ref: ChangeDetectorRef) {

  }

  ngOnInit() {
    this.control.valueChanges.subscribe(() => {
        this.ref.markForCheck();
      });
  }

看看这个stackblitz