Angular 2:使用NG_VALIDATORS的自定义组件问题

时间:2017-03-02 19:53:24

标签: angular angular2-forms

我正在构建一个自定义组件,它包装多个表单字段并实现ControlValueAccessor以与其父组件中的被动表单进行交互。我还使用NG_VALIDATORS设置自定义表单组件的验证,基本上遵循本指南:

https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html

然后通过<div>标记*ngIf来打开/关闭子表单。

我面临的问题是验证器功能正在重新连接&#34;每次组件显示然后隐藏一次。记住每个先前对验证器函数的引用,因此如果要隐藏控件然后再次显示5次,则验证器将针对每个更改检测周期而不是仅一次触发5次。

感谢任何帮助或指导。

为了证明问题,我在验证功能中添加了console.log

plunkr:https://plnkr.co/edit/nIj6WR?p=preview

应用组件

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Component, OnInit } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { ChildFormComponent } from './child-form.component';

@Component({
  selector: 'app-root',
  template: `
<h2>
  Form Value
</h2>
<div>
  <pre>
  {{form.value | json}}
</pre>
</div>
<form [formGroup]="form">
  <label for="value1">Value 1</label>
  <input id="value1" type="text" formControlName="value1" />
  <h3>Show child form?</h3>
  <div>
    <label for="showChildFormYes">Yes</label>
    <input id="showChildFormYes" type="radio" formControlName="showChildForm" value="yes" />
    <label for="showChildFormNo">No</label>
    <input id="showChildFormNo" type="radio" formControlName="showChildForm" value="no" />
  </div>
  <div *ngIf="showChildForm">
    <app-child-form formControlName="childValue"></app-child-form>
  </div>
</form>
`
})
export class AppComponent implements OnInit {
  form: FormGroup;
  showChildForm: boolean;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit() {
    console.log('initializing app form');

    this.form = this.formBuilder.group({
      value1: '',
      childValue: '',
      showChildForm: 'no'
    });

    this.form.get('showChildForm').valueChanges.subscribe(value => {
      this.showChildForm = value === 'yes';
    });
  }
}

@NgModule({
  declarations: [
    AppComponent,
    ChildFormComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

子表单组件

import {
  Component,
  OnInit,
  OnDestroy,
  forwardRef
} from '@angular/core';

import {
  FormGroup,
  FormControl,
  FormBuilder,
  ControlValueAccessor,
  NG_VALUE_ACCESSOR,
  NG_VALIDATORS,
  ValidatorFn
} from '@angular/forms';

const CHILD_FORM_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => ChildFormComponent),
  multi: true
};

const CHILD_FORM_VALIDATORS = {
  provide: NG_VALIDATORS,
  useExisting: forwardRef(() => ChildFormComponent),
  multi: true
};

function validatorFnFactory() {
  return (control: FormControl): ValidatorFn => {
    console.log('VALIDATING', control.value);
    return null;
  }
}

@Component({
  selector: 'app-child-form',
  template: `
<p>
  child-form works!
</p>
<form [formGroup]="form">
  <input type="text" formControlName="value1" />
</form>
  `,
  providers: [
    CHILD_FORM_VALUE_ACCESSOR,
    CHILD_FORM_VALIDATORS
  ]
})
export class ChildFormComponent implements OnInit, OnDestroy, ControlValueAccessor {
  form: FormGroup;
  validator: ValidatorFn;

  constructor(private formBuilder: FormBuilder) {
    this.validator = validatorFnFactory();
  }

  ngOnInit() {
    console.log('initializing child form');

    this.form = this.formBuilder.group({
      value1: ''
    });

    this.form.valueChanges.subscribe(value => {
      this.value = value;
    })
  }

  ngOnDestroy() {
    console.log('destroying child form');
  }

  validate(control: FormControl) {
    this.validator(control);
  }

  _value: any = '';

  get value(): any { return this._value; };

  set value(v: any) {
    if (v !== this._value) {
      this._value = v;
      this.onChange(v);
    }
  }

  writeValue(value: any) {
    this._value = value;
    this.onChange(value);
  }

  onChange = (_) => { };
  onTouched = () => { };
  registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
  registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

1 个答案:

答案 0 :(得分:0)

我没有详细查看您的代码,但*ngIf不只是显示/隐藏一个标记。 评估并将其插入DOM中,或者从DOM中删除

因此,您描述的行为很有意义:每次*ngIf评估为true时,都会重新评估<app-child-form>并重新触发相关的验证。

快速解决方法是使用CSS或DOM hidden属性实际显示/隐藏标记,即<div [hidden]="!showChildForm">

附注:您的*ngIf可能会遇到另一个问题。仅仅因为从表单模板中删除<app-child-form>,就不会将其从模型this.form = this.formBuilder.group(...))中删除。您最终可能会遇到模型显示需要子表单但您在模板中没有此表单的标记(并且没有值)的情况。