使用 OnPush 策略对子元素进行 ChangeDetection
ChangeDetection on child element with OnPush strategy
我有一个 InputComponent
,它作为几个本机输入的包装器并添加了一些逻辑。
此组件有一个输入 control
,它接受 FormControl
并通过 [formControl]
将其绑定到输入字段。
为了提高性能,我已将 changeDetectionStrategy
设置为 OnPush。
现在我注意到,当我使用 formGroup.get(...).setValidators(...)
时,InputComponent 不会更新其状态。我该如何执行此操作?我没有看到验证器更改周期有任何关联。
作为解决方法,我添加了 public API 来手动调用 detectChanges()
.
是否有一个 Observable 可以让我在验证规则发生变化时监听并调用 detectChanges()
而不会导致调用堆栈大小超过。
这可以解释为如何在使用 OnPush 检测策略时调用整个树的变化检测
小 example 在这里您可以看到一个复选框。点击按钮,看到没有区别。但是,如果您单击复选框,它将显示一个星号。
同样有趣的是,当 requiredTrue
被分配给控件并且控件没有 true
作为值时,表单仍然有效。我不明白为什么。
这里的实际问题是将验证器设置在外部破坏了不变性的概念。您拥有的输入是一个对象,其中仅更改了该对象内的 属性 。 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
我有一个 InputComponent
,它作为几个本机输入的包装器并添加了一些逻辑。
此组件有一个输入 control
,它接受 FormControl
并通过 [formControl]
将其绑定到输入字段。
为了提高性能,我已将 changeDetectionStrategy
设置为 OnPush。
现在我注意到,当我使用 formGroup.get(...).setValidators(...)
时,InputComponent 不会更新其状态。我该如何执行此操作?我没有看到验证器更改周期有任何关联。
作为解决方法,我添加了 public API 来手动调用 detectChanges()
.
是否有一个 Observable 可以让我在验证规则发生变化时监听并调用 detectChanges()
而不会导致调用堆栈大小超过。
这可以解释为如何在使用 OnPush 检测策略时调用整个树的变化检测
小 example 在这里您可以看到一个复选框。点击按钮,看到没有区别。但是,如果您单击复选框,它将显示一个星号。
同样有趣的是,当 requiredTrue
被分配给控件并且控件没有 true
作为值时,表单仍然有效。我不明白为什么。
这里的实际问题是将验证器设置在外部破坏了不变性的概念。您拥有的输入是一个对象,其中仅更改了该对象内的 属性 。 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