Angular2 - 自定义组件中的前向字段验证

时间:2017-01-29 01:32:25

标签: angular angular2-forms angular2-components

我正在创建一个包含输入字段的自定义表单控件组件(电子邮件),我转发输入字段的值(正确完成),但也希望转发错误

使用以下代码,当输入发生变化时,我成功传递了字段的错误,但没有正确地检测到初始错误。

例如,即使在视图完全加载并且值为' example@email.com'之后,此电子邮件字段仍会报告错误= {' required':true}。被传递给它。开始输入字段,它将正确传递错误。

所以我的问题是,如何在初始加载数据后传递错误?

注意:运行this.propagateChange(this.value)可以解决问题;在DoCheck的生命周期中,我不喜欢它,我需要更有效的东西,但似乎没有其他钩子可以做到这一点。

以下是示例:

import {Component, forwardRef, Input, ViewChild} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, NgModel} from '@angular/forms';

@Component({
  selector: 'input-email',
  template:`<form-group
  [errors]="{'required':'Email required', 'email':'Invalid email format'}"
  [info]="'Email*'"
>

  <input
    type        = "email"
    name        = "email"
    class       = "form-control"
    [(ngModel)] = "value"
    placeholder = "{{placeholder}}"
    (input)="onChange()"
    email
    required
    #f          = "ngModel"
  >
  {{f.errors | json}}
</form-group>`,
  styleUrls: ['./email.component.css'],
  providers: [
    { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputEmailComponent), multi: true },
    { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputEmailComponent), multi: true }
  ]
})
export class InputEmailComponent implements ControlValueAccessor {


  value:String = null;

  @ViewChild('f') f:NgModel;

  @Input()
  placeholder:String = "Email";

  propagateChange:any = (val) => {};

  constructor() {}



  onChange(){
    this.propagateChange(this.value);
  }

  /**
   * Write a passed NgValue value to the element.
   */
  writeValue(value) {
    if (value && this.value != value) {
      this.value = value;
    }
  }

  /**
   * Set the function to be called
   * when the control receives a change event.
   * registers 'fn' that will be fired when changes are made
   * this is how we emit the changes back to the form
   */
  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  /**
   * Set the function to be called
   * when the control receives a touch event.
   */
  registerOnTouched(fn) {}


  /**
   * Set the function to be called
   * to validate if input has errors.
   */
  validate(c: FormControl):any {

    console.log('validate email');
    return this.f.errors;

  }
}

3 个答案:

答案 0 :(得分:1)

使用NG_ASYNC_VALIDATORS而不是NG_VALIDATORS可以解决问题, 我在这里发布了适合我的解决方案:

function getElementsByClassName(a, b) { var c = []; var d = a.getElementsByTagName("*"); for (var e = 0; e < d.length; e++) { if (d[e].className.indexOf(b) != -1) { c.push(d[e]) } } return c } function DisableDates(a) { if (a._element.className.indexOf("disable_past_dates") != -1) { var b = getElementsByClassName(a._days, "ajax__calendar_day"); var c = (new Date).setHours(0, 0, 0, 0); for (var d = 0; d < b.length; d++) { try { if (b[d].date.setHours(0, 0, 0, 0) >= c) { b[d].className = "ajax__calendar_day" } else { b[d].className = "ajax__calendar_day ajax__calendar_day_disabled" } } catch (e) { } } } if (a._element.className.indexOf("disable_future_dates") != -1) { var b = getElementsByClassName(a._days, "ajax__calendar_day"); var c = (new Date).setHours(0, 0, 0, 0); for (var d = 0; d < b.length; d++) { try { if (b[d].date.setHours(0, 0, 0, 0) <= c) { b[d].className = "ajax__calendar_day" } else { b[d].className = "ajax__calendar_day ajax__calendar_day_disabled" } } catch (e) { } } } } AjaxControlToolkit.CalendarBehavior.prototype.show = function (a) { this._ensureCalendar(); if (!this._isOpen) { var b = new Sys.CancelEventArgs; this.raiseShowing(b); if (b.get_cancel()) { return } this._isOpen = true; this._popupBehavior.show(); if (this._firstPopUp) { this._switchMonth(null, true); switch (this._defaultView) { case AjaxControlToolkit.CalendarDefaultView.Months: this._switchMode("months", true); break; case AjaxControlToolkit.CalendarDefaultView.Years: this._switchMode("years", true); break } this._firstPopUp = false } this.raiseShown(); DisableDates(this) } }; AjaxControlToolkit.CalendarBehavior.prototype._cell_onclick = function (a) { a.stopPropagation(); a.preventDefault(); if (!this._enabled) return; var b = a.target; var c = this._getEffectiveVisibleDate(); Sys.UI.DomElement.removeCssClass(b.parentNode, "ajax__calendar_hover"); switch (b.mode) { case "prev": case "next": this._switchMonth(b.date); break; case "title": switch (this._mode) { case "days": this._switchMode("months"); break; case "months": this._switchMode("years"); break } break; case "month": if (b.month == c.getMonth()) { this._switchMode("days") } else { this._visibleDate = b.date; this._switchMode("days") } break; case "year": if (b.date.getFullYear() == c.getFullYear()) { this._switchMode("months") } else { this._visibleDate = b.date; this._switchMode("months") } break; case "day": if (this._element.className.indexOf("disable_past_dates") != -1) { if (b.date.setHours(0, 0, 0, 0) >= (new Date).setHours(0, 0, 0, 0)) { this.set_selectedDate(b.date); this._switchMonth(b.date); this._blur.post(true); this.raiseDateSelectionChanged() } } else if (this._element.className.indexOf("disable_future_dates") != -1) { if (b.date.setHours(0, 0, 0, 0) <= (new Date).setHours(0, 0, 0, 0)) { this.set_selectedDate(b.date); this._switchMonth(b.date); this._blur.post(true); this.raiseDateSelectionChanged() } } else { this.set_selectedDate(b.date); this._switchMonth(b.date); this._blur.post(true); this.raiseDateSelectionChanged() } break; case "today": this.set_selectedDate(b.date); this._switchMonth(b.date); this._blur.post(true); this.raiseDateSelectionChanged(); break } DisableDates(this) };

答案 1 :(得分:0)

由于问题FormGroup & FormControl statusChanges are not emitted on creation #14542,我在采用您的提案时遇到了麻烦。

因此,我进行了同步。验证器是这样的:

export class MyInputComponent implements AfterViewInit, ControlValueAccessor, Validator {
  ...
  @ViewChild('datePicker') public datePickerInput: NgbInputDatepicker;
  ...
  ngAfterViewInit(): void {
    this.ngControl = this.injector.get(NgControl);

    // Force restart of validation
    if (this.ngControl && this.ngControl.control) {
      this.ngControl.control.updateValueAndValidity({
        onlySelf: true
      });
    }
  }
  ...
  public validate(control: AbstractControl): ValidationErrors | null {
    return this.datePickerInput ? this.datePickerInput.validate(control) : null;
  }

答案 2 :(得分:0)

我通过在onValidatorChange中调用Validator回调(来自ngAfterViewInit接口)来解决此问题:

  ngAfterViewInit(): void {
    // Force the validation to trigger after the view has been initialised
    // otherwise the initial value is not marked as invalid
    if (this.onValidatorChange) {
      // you need a setTimeout here to make the change after Angular's change detection
      setTimeout(() => {
        this.onValidatorChange();
        this.cdr.markForCheck();
      }, 0);
    }
  }

  registerOnValidatorChange(fn: () => void): void {
    this.onValidatorChange = fn
  }