Angular 使用 onPush 时输入未正确触发
Angular input not triggering properly when using onPush
我刚刚开始使用 ControlValueAccessor,到目前为止我真的很喜欢它,但我遇到了一个我无法解决的问题..
@Component({
selector: 'app-project',
templateUrl: './project.component.html',
styleUrls: ['./project.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProjectComponent implements ControlValueAccessor, OnInit {
@Input('companyNumber') set companyNumber(companyNumber: string) {
this._companyNumber = companyNumber;
}
ctrlErrorStateMatcher = new CtrlErrorStateMatcher();
private _companyNumber: string;
constructor(
private readonly _costCenterService: CostCenterService,
@Optional() @Self() public ngControl: NgControl,
) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
onTouched = (_value?: any) => { };
onChanged = (_value?: any) => { };
writeValue(val: string): void {
if (val) {
this.ngControl.control?.setValue(val);
}
}
registerOnChange(fn: any): void {
this.onChanged = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
ngOnInit() {
this.ngControl.control.setAsyncValidators(this.validate.bind(this));
this.ngControl.control.updateValueAndValidity();
}
validate(control: AbstractControl): Observable<ValidationErrors | null> {
const isValid = this._costCenterService
.validateProject(control.value, this._companyNumber)
.pipe(
map(response => {
return response ? null : { inValidProjectNumber: true };
})
);
return isValid;
}
}
export class CtrlErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl): boolean {
return !!(control && control.invalid);
}
}
因此,当我更改输入中的值(不仅仅是触发 (blur) )时,这一切工作正常。但是当 @Input 更改时,它不会更新状态(触发 validate() )。 .
如果我随后更改输入中的值,它确实有效...删除 OnPush 可解决问题,但这不是我想要做的...
更新:模板
<mat-form-field appearance="outline">
<mat-label>Label</mat-label>
<input matInput #projectInput [value]="ngControl.value" [formControl]="ngControl?.control"
(change)=" onChanged($event.target.value)" (blur)="onTouched()" [errorStateMatcher]="ctrlErrorStateMatcher" />
<mat-error *ngIf="ngControl.hasError('inValidProjectNumber')">
Error
</mat-error>
</mat-form-field>
我发现了这个 git 问题 https://github.com/angular/angular/issues/12378
所以对我来说,解决方案是添加一个 finalize 管道并使用 markForCheck()
validate(control: AbstractControl): Observable<ValidationErrors | null> {
const isValid = this._costCenterService
.validateProject(control.value, this._companyNumber)
.pipe(
map(response => {
return response ? null : { inValidProjectNumber: true };
}),
finalize(() => this.changeDetectorRef.markForCheck())
);
return isValid;
}
我刚刚开始使用 ControlValueAccessor,到目前为止我真的很喜欢它,但我遇到了一个我无法解决的问题..
@Component({
selector: 'app-project',
templateUrl: './project.component.html',
styleUrls: ['./project.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProjectComponent implements ControlValueAccessor, OnInit {
@Input('companyNumber') set companyNumber(companyNumber: string) {
this._companyNumber = companyNumber;
}
ctrlErrorStateMatcher = new CtrlErrorStateMatcher();
private _companyNumber: string;
constructor(
private readonly _costCenterService: CostCenterService,
@Optional() @Self() public ngControl: NgControl,
) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
onTouched = (_value?: any) => { };
onChanged = (_value?: any) => { };
writeValue(val: string): void {
if (val) {
this.ngControl.control?.setValue(val);
}
}
registerOnChange(fn: any): void {
this.onChanged = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
ngOnInit() {
this.ngControl.control.setAsyncValidators(this.validate.bind(this));
this.ngControl.control.updateValueAndValidity();
}
validate(control: AbstractControl): Observable<ValidationErrors | null> {
const isValid = this._costCenterService
.validateProject(control.value, this._companyNumber)
.pipe(
map(response => {
return response ? null : { inValidProjectNumber: true };
})
);
return isValid;
}
}
export class CtrlErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl): boolean {
return !!(control && control.invalid);
}
}
因此,当我更改输入中的值(不仅仅是触发 (blur) )时,这一切工作正常。但是当 @Input 更改时,它不会更新状态(触发 validate() )。 .
如果我随后更改输入中的值,它确实有效...删除 OnPush 可解决问题,但这不是我想要做的...
更新:模板
<mat-form-field appearance="outline">
<mat-label>Label</mat-label>
<input matInput #projectInput [value]="ngControl.value" [formControl]="ngControl?.control"
(change)=" onChanged($event.target.value)" (blur)="onTouched()" [errorStateMatcher]="ctrlErrorStateMatcher" />
<mat-error *ngIf="ngControl.hasError('inValidProjectNumber')">
Error
</mat-error>
</mat-form-field>
我发现了这个 git 问题 https://github.com/angular/angular/issues/12378
所以对我来说,解决方案是添加一个 finalize 管道并使用 markForCheck()
validate(control: AbstractControl): Observable<ValidationErrors | null> {
const isValid = this._costCenterService
.validateProject(control.value, this._companyNumber)
.pipe(
map(response => {
return response ? null : { inValidProjectNumber: true };
}),
finalize(() => this.changeDetectorRef.markForCheck())
);
return isValid;
}