我的 Angular 应用程序中有几个自定义表单控件组件,它们实现ControlValueAccessor
了接口并且效果很好。
但是,当markAsPristine()
在父窗体上或直接在我的自定义控件上调用时,我需要更新它的状态:我的自定义控件实际上具有内部控件,我也需要调用markAsPristine()
它。
所以,我怎么知道什么时候markAsPristine()
在我的控件上被调用?
该ControlValueAccessor
接口没有成员,与这个问题有关,我可以实现。
我的 Angular 应用程序中有几个自定义表单控件组件,它们实现ControlValueAccessor
了接口并且效果很好。
但是,当markAsPristine()
在父窗体上或直接在我的自定义控件上调用时,我需要更新它的状态:我的自定义控件实际上具有内部控件,我也需要调用markAsPristine()
它。
所以,我怎么知道什么时候markAsPristine()
在我的控件上被调用?
该ControlValueAccessor
接口没有成员,与这个问题有关,我可以实现。
经过彻底调查,我发现 Angular 并没有专门提供此功能。我已经在官方存储库中发布了一个关于此的问题,并且它已获得功能请求状态。我希望它会在不久的将来实施。
在那之前,这里有两种可能的解决方法:
markAsPristine()
@Component({
selector: 'my-custom-form-component',
templateUrl: './custom-form-component.html',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: MyCustomFormComponent,
multi: true
}]
})
export class MyCustomFormComponent implements ControlValueAccessor, OnInit {
private control: AbstractControl;
ngOnInit() {
const origFunc = this.control.markAsPristine;
this.control.markAsPristine = function() {
origFunc.apply(this, arguments);
console.log('Marked as pristine!');
}
}
}
ngDoCheck
请注意,此解决方案的性能可能会降低,但它为您提供了更好的灵活性,因为您可以监控原始状态何时发生变化。markAsPristine()
在上面的解决方案中,只有在被调用时才会通知您。
@Component({
selector: 'my-custom-form-component',
templateUrl: './custom-form-component.html',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: MyCustomFormComponent,
multi: true
}]
})
export class MyCustomFormComponent implements ControlValueAccessor, DoCheck {
private control: AbstractControl;
private pristine = true;
ngDoCheck(): void {
if (this.pristine !== this.control.pristine) {
this.pristine = this.control.pristine;
if (this.pristine) {
console.log('Marked as pristine!');
}
}
}
}
如果你需要FormControl
从你的组件访问实例,请看这个问题:Get access to FormControl from the custom form component in Angular。
我的解决方法的灵感来自 Slava 的帖子和从 Angular 中的自定义表单组件获取对 FormControl 的访问权限,并将模板表单 (ngModel) 和反应式表单混合在一起。组件内的复选框控件反映脏/原始状态并将其状态报告回外部以形成组。所以我可以将样式应用于<label>
基于类 ng-dirty、ng-valid 等的复选框输入 () 控件。我没有实现 markAsTouched、markAsUntouched,因为它可以以类似的方式完成。StackBlitz上的工作演示
示例组件代码为:
import { AfterViewInit, Component, Input, OnInit, Optional, Self, ViewChild } from "@angular/core";
import { ControlValueAccessor, NgControl, NgModel } from "@angular/forms";
@Component({
selector: "app-custom-checkbox-control",
template: '<input id="checkBoxInput"\
#checkBoxNgModel="ngModel"\
type="checkbox"\
name="chkbxname"\
[ngModel]="isChecked"\
(ngModelChange)="checkboxChange($event)"\
>\
<label for="checkBoxInput">\
{{description}}\
</label>\
<div>checkbox dirty state: {{checkBoxNgModel.dirty}}</div>\
<div>checkbox pristine state: {{checkBoxNgModel.pristine}}</div>',
styleUrls: ["./custom-checkbox-control.component.css"]
})
export class CustomCheckboxControlComponent
implements OnInit, AfterViewInit, ControlValueAccessor {
disabled: boolean = false;
isChecked: boolean = false;
@Input() description: string;
@ViewChild('checkBoxNgModel') checkBoxChild: NgModel;
constructor(@Optional() @Self() public ngControl: NgControl) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
checkboxChange(chk: boolean) {
console.log("in checkbox component: Checkbox changing value to: ", chk);
this.isChecked = chk;
this.onChange(chk);
}
ngOnInit() {}
ngAfterViewInit(): void {
debugger
this.checkBoxChild.control.setValidators(this.ngControl.control.validator);
const origFuncDirty = this.ngControl.control.markAsDirty;
this.ngControl.control.markAsDirty = () => {
origFuncDirty.apply(this.ngControl.control, arguments);
this.checkBoxChild.control.markAsDirty();
console.log('in checkbox component: Checkbox marked as dirty!');
}
const origFuncPristine = this.ngControl.control.markAsPristine;
this.ngControl.control.markAsPristine = () => {
origFuncPristine.apply(this.ngControl.control, arguments);
this.checkBoxChild.control.markAsPristine();
console.log('in checkbox component: Checkbox marked as pristine!');
}
}
//ControlValueAccessor implementations
writeValue(check: boolean): void {
this.isChecked = check;
}
onChange = (val: any) => {};
onTouched = () => {};
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}
还有另一种检查表单是否脏的方法。我们可以比较表单绑定到的对象。下面的函数可以用于对象属性比较
isEquivalent(a, b) {
// Create arrays of property names
var aProps = Object.getOwnPropertyNames(a);
var bProps = Object.getOwnPropertyNames(b);
// If number of properties is different,
// objects are not equivalent
if (aProps.length != bProps.length) {
return false;
}
for (var i = 0; i < aProps.length; i++) {
var propName = aProps[i];
// If values of same property are not equal,
// objects are not equivalent
if (a[propName] !== b[propName]) {
return false;
}
}
// If we made it this far, objects
// are considered equivalent
return true;
}
如果您想在 stackblitz 链接下方检查此用途。我已经对其进行了测试并且运行良好。 Stackblitz 链接
根据 Slava 的回答,另一个建议是替换类中的markAsDirty
、markAsPristine
和_updatePristine
方法FormGroup
:
ngOnInit(): void {
const markAsDirty = this.formGroup.markAsDirty;
this.formGroup.markAsDirty = (opts) => {
markAsDirty.apply(this.formGroup, opts);
console.log('>>>>> markAsDirty');
};
const markAsPristine = this.formGroup.markAsPristine;
this.formGroup.markAsPristine = (opts) => {
markAsPristine.apply(this.formGroup, opts);
console.log('>>>>> markAsPristine');
};
const updatePristine = this.formGroup['_updatePristine'];
this.formGroup['_updatePristine'] = (opts) => {
updatePristine.apply(this.formGroup, opts);
console.log('>>>>> updatePristine');
};
}
我在这些console.log
位置发出事件,但当然,其他方法也可以。