Angular 2自定义输入组件验证(新表单api)

时间:2016-08-11 00:55:47

标签: angular angular2-forms

我目前正在使用Angular 2 RC4和新的表单API开发一个很长的表单。

我有自己的输入组件,并希望进一步处理验证。

我试图让我的验证器使用我的输入,但验证器更新组件InputFieldComponent而不是我的输入字段......

这是我的HTML:

<label [attr.for]="inputId"><ng-content></ng-content></label>
<small class="text-muted">{{helptext}}</small>
<small [hidden]="(input.valid) || (input.untouched && input.pristine)" class="text-danger">
  {{errormsg}}
</small>
<input
       class="form-control custom-input"
       [id]="inputId"
       [required]="required"
       [type]="type"
       [attr.name]="name"
       [(ngModel)]="value"
       [pattern]="pattern"
       #input="ngModel"
       >

通过

调用
<custom-input-field
  name="birthDate"
  [(ngModel)]="model.birthDate"
  placeholder="DD/MM/YYYY"
  helptext="Date of birth"
  required="true"
  pattern="^[0-3]?[0-9]\/[01]?[0-9]\/[12][90][0-9][0-9]$"
  errormsg="The date of birth is mandatory and should be entered as dd/mm/yyyy"
>
  Date of birth

</custom-input-field>

这是我的代码:

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

const noop = () => {};

let nextUniqueId = 0;

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => InputFieldComponent),
  multi: true
};

@Component({
  moduleId: module.id,
  selector: 'custom-input-field',
  styleUrls: ['input-field.component.css'],
  templateUrl: 'input-field.component.html',
  providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})


export class InputFieldComponent implements ControlValueAccessor {
  //the external properties
  @Input() id: string = `custom-field-${nextUniqueId++}`;
  @Input() helptext: string = null;
  @Input() placeholder: string = null;
  @Input() required: boolean = false;
  @Input() type: string = 'text';
  @Input() name: string = null;

  //The internal data model
  private innerValue: any = '';

  //Placeholders for the callbacks which are later provided
  //by the Control Value Accessor
  private onTouchedCallback: () => void = noop;
  private onChangeCallback: (_: any) => void = noop;

  //get accessor
  get value(): any {
    return this.innerValue;
  };

  //set accessor including call the onchange callback
  set value(v: any) {
    if (v !== this.innerValue) {
      this.innerValue = v;
      this.onChangeCallback(v);
    }
  }

  //From ControlValueAccessor interface
  // The writeValue function allows you to update your internal model with incoming values,
  // for example if you use ngModel to bind your control to data.
  writeValue(value: any) {
    if (value !== this.innerValue) {
      this.innerValue = value;
    }
  }

  //NOTE: Both following functions are later provided by Angular 2 itself. But we need to register dummy functions to be
  // able code and transpile it without errors.

  //From ControlValueAccessor interface
  registerOnChange(fn: any) {
    this.onChangeCallback = fn;
  }

  //From ControlValueAccessor interface
  registerOnTouched(fn: any) {
    this.onTouchedCallback = fn;
  }

  //** Read-only properties */
  get inputId(): string { return `${this.id}-input`; }
};

1 个答案:

答案 0 :(得分:1)

我终于找到了解决方法来实现我想要的目标。

  • 由于模板变量不能动态
  • 我很难访问我的组件属性并在HTML视图中动态测试它们的值(问题如Expression 'xxxx' was changed after it was checked

最后使用自定义组件阴影dom来测试组件是否有效并且它是否有效并使代码更轻。

这是我的自定义输入组件的代码及其CSS选择器

@Component({
  moduleId: module.id,
  selector: 'custom-input-field',
  templateUrl: 'input-field.component.html',
  providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR],
  directives: [DefaultValueAccessor, NgModel],
  host: {
    '(click)': 'focus()',
    '[class.custom-input]': 'true',
  },

  /**
   * This styles the input error msg that is integrated into our component using the automatic classes
   * that ngModel updates on the component.
   *
   * It uses the shadow-dom and therefore has to be declared line by line in the component file
   * (does not seem to work with Sass transpiler)
   */
  styles: [
    `
    :host.ng-touched.ng-invalid >>> input {
       border-left: 5px solid #a94442; /* red */
    }

    :host.ng-touched.ng-valid >>> input {
        border-left: 5px solid #42A948; /* green */
    }

    :host.ng-valid:not([required]).ng-touched.ng-dirty >>> input {
        border-left: 5px solid #42A948; /* green */
    }

    :host.ng-pristine >>> .error-msg {
        display:none;
    }

    :host.ng-valid >>> .error-msg {
        display:none;
    }

    :host.ng-untouched >>> .error-msg {
        display:none;
    }

    :host.ng-touched.ng-invalid >>> .error-msg {
       display:inline;
    }

    .text-danger { font-weight: 500; }
}
  `]
})

这里是模板内容(丰富了更多属性):

<label [attr.for]="inputId"><ng-content></ng-content></label>
<small class="text-muted">{{helptext}}</small>
<small class="error-msg text-danger">
  <ng-content select="input-error"></ng-content>
</small>

<input #input
       class="form-control custom-input"
       [disabled]="disabled"
       [id]="inputId"
       [attr.list]="list"
       [attr.max]="max"
       [attr.maxlength]="maxLength"
       [attr.min]="min"
       [attr.minlength]="minLength"
       [readonly]="readOnly"
       [required]="required"
       [spellcheck]="spellCheck"
       [attr.step]="step"
       [attr.tabindex]="tabIndex"
       [type]="type"
       [attr.name]="name"
       (focus)="_handleFocus($event)"
       (blur)="_handleBlur($event)"
       (change)="_handleChange($event)"
       [(ngModel)]="value"
>

我这样称呼我的组件:

<custom-input-field
              name="birthDate"
              [(ngModel)]="model.birthDate"
              placeholder="JJ/MM/AAAA"
              helptext="format jj/mm/aaaa"
              #birthDate="ngModel"
              required="true"
              pattern="^[0-3]?[0-9]\/[01]?[0-9]\/[12][90][0-9][0-9]$"
            >
              Date of birth
              <input-error><br />The date of birth is mandatory and should be entered as dd/mm/yyyy</input-error>
            </custom-input-field>