我如何知道自定义表单控件何时在 Angular 中标记为原始?

How do I know when custom form control is marked as pristine in Angular?

我的 Angular 应用程序中有几个自定义表单控件组件,它们实现了 ControlValueAccessor 接口并且效果很好。

但是,当 markAsPristine() 在父窗体上调用时,或者直接在我的自定义控件上调用时,我需要更新它的状态:我的自定义控件实际上有内部控件,我需要调用 markAsPristine()也在上面。

那么,我怎么知道什么时候在我的控件上调用了 markAsPristine()

ControlValueAccessor接口没有成员,与此问题相关,我可以实现。

经过彻底调查后,我发现 Angular 并未专门提供此功能。我已经在官方存储库中 posted an issue 了解了这一点,并且它已获得功能请求状态。希望在不久的将来实现。


在那之前,这里有两个可能的解决方法

猴子修补 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 实例,请参阅此问题:.

根据 Slava 的回答,另一个建议是替换 FormGroup class 中的 markAsDirtymarkAsPristine_updatePristine 方法:

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 位置发出事件,但其他方法当然也可行。

还有另一种方法可以检查表单是否脏了。我们可以比较通过哪个表单绑定的对象to.Below 函数可以用于对象属性比较

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 link 下方查看此用法。 我已经对其进行了测试并且运行良好。 Stackblitz link

我的解决方法受到 Slava 的 post 和 的启发,并将模板表单 (ngModel) 和反应式表单混合在一起。 组件内的复选框控件反映 dirty/pristine 状态并将其状态报告回外部以形成组。因此,我可以基于 类 ng-dirty、ng-valid 等将样式应用于复选框输入 (<label>) 控件。 我还没有实现 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;
  }
}