【问题标题】:Angular 2: Issue with custom component using NG_VALIDATORSAngular 2:使用 NG_VALIDATORS 的自定义组件问题
【发布时间】:2017-03-02 19:53:24
【问题描述】:

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

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

然后通过使用*ngIf 包裹在<div> 标记中来打开/关闭子表单。

我面临的问题是,每次显示然后隐藏组件时,验证器功能都会“重新连接”一次。之前对验证器函数的每个引用都会被记住,因此如果要隐藏控件然后再次显示 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; }
}

【问题讨论】:

    标签: angular angular2-forms


    【解决方案1】:

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

    所以您描述的行为是有道理的:每次您的 *ngIf 评估为 true 时,&lt;app-child-form&gt; 都会重新评估并再次触发相关的验证。

    一个快速的解决方法是实际显示/隐藏标记使用 CSS 或 DOM hidden 属性,即&lt;div [hidden]="!showChildForm"&gt;

    旁注:您的*ngIf 可能会遇到另一个问题。仅仅因为您从表单 template 中删除 &lt;app-child-form&gt; 并不会从表单 model (this.form = this.formBuilder.group(...)) 中删除它。您最终可能会遇到模型说需要子表单但您在模板中没有此表单的标记(也没有值)的情况。

    【讨论】:

    • 删除控件并将其重新添加到 FormGroup,同时将实际控件包裹在模板中的 *ngIf 指令中解决了我遇到的问题。
    猜你喜欢
    • 2016-05-06
    • 2021-03-20
    • 1970-01-01
    • 1970-01-01
    • 2017-02-20
    • 1970-01-01
    • 1970-01-01
    • 2017-04-11
    • 2018-04-11
    相关资源
    最近更新 更多