22

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

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

所以,我怎么知道什么时候markAsPristine()在我的控件上被调用?

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

4

4 回答 4

21

经过彻底调查,我发现 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

于 2017-06-24T03:58:50.483 回答
0

我的解决方法的灵感来自 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;
  }
}
于 2020-12-28T22:03:36.477 回答
0

还有另一种检查表单是否脏的方法。我们可以比较表单绑定到的对象。下面的函数可以用于对象属性比较

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 链接

于 2019-06-03T11:48:36.187 回答
0

根据 Slava 的回答,另一个建议是替换类中的markAsDirtymarkAsPristine_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位置发出事件,但当然,其他方法也可以。

于 2018-05-10T06:25:23.773 回答