表单组值更改时不会触发更改检测
Change detection does not trigger when the formgroup values change
我创建了一个简单示例来演示我面临的一个奇怪问题。
Stackblitz - https://stackblitz.com/edit/angular-change-detection-form-group
我有三个组成部分,它们在这里:
1 - 应用组件
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'my-app',
template: `<hello [form]="form"></hello>
<hr />
<button (click)="changeFormValue()">Change Form Value</button>`,
styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements OnInit {
name = 'Angular';
form: FormGroup;
ngOnInit() {
this.form = new FormGroup({
name: new FormControl('ABC'),
age: new FormControl('24')
});
}
changeFormValue() {
this.form.setValue({
name: 'XYZ',
age: 35
})
}
}
2 - 你好组件
import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'hello',
template: `<form [formGroup]="form">
<app-input [form]="form"></app-input>
</form>`,
styles: [``],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloComponent implements OnChanges {
@Input() form: FormGroup;
ngOnChanges(changes) {
console.log(changes)
}
}
3 - 输入组件
import { Component, Input, OnInit, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-input',
template: `Name : <input type="text" [formControl]="nameFormcontrol" /> {{nameFormcontrol.value}} <br /><br />
Age : <input type="text" [formControl]="ageFormcontrol" /> {{ageFormcontrol.value}}`,
styles: [``],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InputComponent implements OnInit, OnChanges {
@Input() form: FormGroup;
nameFormcontrol;
ageFormcontrol;
ngOnInit() {
this.nameFormcontrol = this.form.get('name');
this.ageFormcontrol = this.form.get('age');
}
ngOnChanges(changes) {
console.log(changes)
}
}
在hello组件和input组件中,我都设置了changedetection策略为onpush。正如您在上面看到的,我在应用程序组件中创建了一个表单组实例并将其传递给子组件。现在,当我单击应用程序组件上的按钮来更改表单值时,它会更改输入字段中的值,但不会更改纯文本。仅当我从两个子组件中删除推送更改检测时它才有效。即使表单组值发生变化,也不会调用 ngOnChanges。
是不是很奇怪。更改检测如何适用于输入而不适用于此处的纯文本?
有人能给我解释一下吗?在不删除 onpush 变化检测的情况下,它的解决方法是什么。
Angular 仅在变量的内存地址发生变化时检测变化。设置该值不会更改内存地址,因此不会命中 ngOnChanges。
数组也是如此。简单的推送不会命中 ngOnChanges,必须通过 = 将内存地址更改为新数组。
试试这个:
import { Component, Input, OnInit, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-input',
template: `
<div formGroupName="form">
Name : <input type="text" formControlName="name" /> {{form.value.name}} <br /><br />
Age : <input type="text" formControlName="age" /> {{form.value.age}}
</div>`,
styles: [``],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InputComponent implements OnInit, OnChanges {
@Input() form: FormGroup;
ngOnInit() {
}
ngOnChanges(changes) {
console.log(changes)
}
}
在自动更改检测 (cd) 期间,运行 Angular 会注意到您的 FormGroup
的值已更新并分别更新 UI。
通过将更改检测策略设置为 OnPush
,您可以通过 Angular 停用自动更改检测 运行。在这种情况下,仅更新内部值,但 Angular 不会检查 UI.
的值
也许这是对 Angular 的 ChangeDetection 的过于肤浅的解释,所以我推荐这个 blog 以便更深入地研究这个主题。
ngOnChanges
没有触发,因为你的FormGroup
的对象引用(内存地址)没有改变。 ngOnChanges
只有在将 primitives 传递给组件的 @Input
时才会触发。这也会触发 Angular.
的新变化检测 运行
要更新 UI,您可以通过在父组件中注入 ChangeDetectorRef
并调用 detectChanges()
.
来手动触发更改检测
这可能是这样的:
constructor(private cd: ChangeDetectorRef) {}
...
changeFormValue() {
this.form.setValue({
name: 'XYZ',
age: 35
});
// This will trigger the change detection and your input field are updated
this.cd.detectChanges();
}
我希望这是可以理解的;-)
虽然我不确定这是否是理想的解决方案,但我找到了解决此问题的方法。
我们可以监听表单组值的变化,然后在input组件中触发变化检测
this.form.valueChanges.subscribe( () => {
this.cdr.detectChanges()
});
通过这种方式,它会连同输入一起更新标签值。
解决方法如下:
https://stackblitz.com/edit/angular-change-detection-form-group-value-change-issue-resolved
我不确定这是否是 Angular 的错误,但很高兴我找到了一些解决方法:)
你可以做得更好!如果这有效,请告诉我:
当你使用响应式表单时,你可以使用一个非常酷的方法,叫做 updateValueAndValidity();
private changeControlValue(control, value: number) {
control.setValue(value);
control.updateValueAndValidity();
}
您也可以在更新添加到表单的验证器时使用此方法,示例:
this.control.setValidators([Validators.required, Validators.minLength(5)]);
control.updateValueAndValidity();
这应该可以解决问题!我认为这是针对 ng-model 使用反应式表单或表单控件的最佳冒险之一。
我不建议在您的表单中使用 valueChanges,因为您很容易忘记取消订阅并造成内存泄漏,即使您记得创建此流程可能很乏味。
请记住,当使用 onPush 变化检测时,只有三件事会被 Angular 检测为变化:
1- 输入和输出。
2- Html 用户可以执行的事件,例如(单击)。
3- 订阅等异步事件。
希望对你有所帮助!
只需使用async
管道在模板中订阅控件的valueChanges
属性,避免了手动触发变化检测和订阅valueChanges
的需要组件。
<input [formControl]="control"/>
<p>{{control.valueChanges | async}}</p>
我创建了一个简单示例来演示我面临的一个奇怪问题。
Stackblitz - https://stackblitz.com/edit/angular-change-detection-form-group
我有三个组成部分,它们在这里:
1 - 应用组件
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'my-app',
template: `<hello [form]="form"></hello>
<hr />
<button (click)="changeFormValue()">Change Form Value</button>`,
styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements OnInit {
name = 'Angular';
form: FormGroup;
ngOnInit() {
this.form = new FormGroup({
name: new FormControl('ABC'),
age: new FormControl('24')
});
}
changeFormValue() {
this.form.setValue({
name: 'XYZ',
age: 35
})
}
}
2 - 你好组件
import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'hello',
template: `<form [formGroup]="form">
<app-input [form]="form"></app-input>
</form>`,
styles: [``],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloComponent implements OnChanges {
@Input() form: FormGroup;
ngOnChanges(changes) {
console.log(changes)
}
}
3 - 输入组件
import { Component, Input, OnInit, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-input',
template: `Name : <input type="text" [formControl]="nameFormcontrol" /> {{nameFormcontrol.value}} <br /><br />
Age : <input type="text" [formControl]="ageFormcontrol" /> {{ageFormcontrol.value}}`,
styles: [``],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InputComponent implements OnInit, OnChanges {
@Input() form: FormGroup;
nameFormcontrol;
ageFormcontrol;
ngOnInit() {
this.nameFormcontrol = this.form.get('name');
this.ageFormcontrol = this.form.get('age');
}
ngOnChanges(changes) {
console.log(changes)
}
}
在hello组件和input组件中,我都设置了changedetection策略为onpush。正如您在上面看到的,我在应用程序组件中创建了一个表单组实例并将其传递给子组件。现在,当我单击应用程序组件上的按钮来更改表单值时,它会更改输入字段中的值,但不会更改纯文本。仅当我从两个子组件中删除推送更改检测时它才有效。即使表单组值发生变化,也不会调用 ngOnChanges。
是不是很奇怪。更改检测如何适用于输入而不适用于此处的纯文本?
有人能给我解释一下吗?在不删除 onpush 变化检测的情况下,它的解决方法是什么。
Angular 仅在变量的内存地址发生变化时检测变化。设置该值不会更改内存地址,因此不会命中 ngOnChanges。
数组也是如此。简单的推送不会命中 ngOnChanges,必须通过 = 将内存地址更改为新数组。
试试这个:
import { Component, Input, OnInit, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-input',
template: `
<div formGroupName="form">
Name : <input type="text" formControlName="name" /> {{form.value.name}} <br /><br />
Age : <input type="text" formControlName="age" /> {{form.value.age}}
</div>`,
styles: [``],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InputComponent implements OnInit, OnChanges {
@Input() form: FormGroup;
ngOnInit() {
}
ngOnChanges(changes) {
console.log(changes)
}
}
在自动更改检测 (cd) 期间,运行 Angular 会注意到您的 FormGroup
的值已更新并分别更新 UI。
通过将更改检测策略设置为 OnPush
,您可以通过 Angular 停用自动更改检测 运行。在这种情况下,仅更新内部值,但 Angular 不会检查 UI.
也许这是对 Angular 的 ChangeDetection 的过于肤浅的解释,所以我推荐这个 blog 以便更深入地研究这个主题。
ngOnChanges
没有触发,因为你的FormGroup
的对象引用(内存地址)没有改变。 ngOnChanges
只有在将 primitives 传递给组件的 @Input
时才会触发。这也会触发 Angular.
要更新 UI,您可以通过在父组件中注入 ChangeDetectorRef
并调用 detectChanges()
.
这可能是这样的:
constructor(private cd: ChangeDetectorRef) {}
...
changeFormValue() {
this.form.setValue({
name: 'XYZ',
age: 35
});
// This will trigger the change detection and your input field are updated
this.cd.detectChanges();
}
我希望这是可以理解的;-)
虽然我不确定这是否是理想的解决方案,但我找到了解决此问题的方法。
我们可以监听表单组值的变化,然后在input组件中触发变化检测
this.form.valueChanges.subscribe( () => {
this.cdr.detectChanges()
});
通过这种方式,它会连同输入一起更新标签值。
解决方法如下:
https://stackblitz.com/edit/angular-change-detection-form-group-value-change-issue-resolved
我不确定这是否是 Angular 的错误,但很高兴我找到了一些解决方法:)
你可以做得更好!如果这有效,请告诉我:
当你使用响应式表单时,你可以使用一个非常酷的方法,叫做 updateValueAndValidity();
private changeControlValue(control, value: number) {
control.setValue(value);
control.updateValueAndValidity();
}
您也可以在更新添加到表单的验证器时使用此方法,示例:
this.control.setValidators([Validators.required, Validators.minLength(5)]);
control.updateValueAndValidity();
这应该可以解决问题!我认为这是针对 ng-model 使用反应式表单或表单控件的最佳冒险之一。
我不建议在您的表单中使用 valueChanges,因为您很容易忘记取消订阅并造成内存泄漏,即使您记得创建此流程可能很乏味。
请记住,当使用 onPush 变化检测时,只有三件事会被 Angular 检测为变化:
1- 输入和输出。 2- Html 用户可以执行的事件,例如(单击)。 3- 订阅等异步事件。
希望对你有所帮助!
只需使用async
管道在模板中订阅控件的valueChanges
属性,避免了手动触发变化检测和订阅valueChanges
的需要组件。
<input [formControl]="control"/>
<p>{{control.valueChanges | async}}</p>