【问题标题】:Angular2 - Create a reusable, validated text input componentAngular2 - 创建一个可重用、经过验证的文本输入组件
【发布时间】:2016-08-31 17:44:52
【问题描述】:

我正在创建一个带有 Node 后端的 Angular2 应用程序。我将有向所述后端提交数据的表单。我希望在客户端和服务器端都进行验证,并且我想避免重复这些验证规则。

以上内容与实际问题有些无关,只是说这就是我不使用传统 Angular2 验证方法的原因。

这给我留下了以下 HTML 结构:

<div class="form-group" [class.has-error]="hasError(name)">
    <label class="control-label" for="name"> Property Name
    <input id="name" class="form-control" type="text" name="name" [(ngModel)]="property.name" #name="ngModel" />
    <div class="alert alert-danger" *ngIf="hasError(name)">{{errors.name}}</div>
</div>

<div class="form-group" [class.has-error]="hasError(address1)">
    <label class="control-label" for="address1"> Address
    <input id="address1" class="form-control" type="text" name="address1" [(ngModel)]="property.address.address1" #address1="ngModel" />
    <div class="alert alert-danger" *ngIf="hasError(address1)">{{errors['address.address1']}}</div>
</div>

我将有一些较大的表格,并希望减少上述内容的冗长。我希望实现类似于以下的目标:

<my-text-input label="Property Name" [(ngModel)]="property.name" name="name"></my-text-input>
<my-text-input label="Address" [(ngModel)]="property.address.address1" name="address1" key="address.address1"></my-text-input>

我正在努力实现这一目标。给我带来麻烦的特定部分是:

  • ngModel 上设置双向绑定(我在组件中所做的更改不会反映回父级)。
  • 基于组件的@Input 变量生成模板引用变量(#name#address1 属性)。
    • 我突然想到,也许我不需要为组件的每个实例使用单独的模板引用变量名。也许我可以只使用#input,因为它只在该组件中被引用。想法?
  • 我可以将errorsconstraints 对象传递给组件的每个实例进行验证,但我想减少重复。

我意识到这是一个有点宽泛的问题,但我相信一个好的答案将对许多用户广泛有用且非常有价值,因为这是一种常见情况。我也意识到我实际上并没有展示我尝试过的东西(只是解释说我确实已经努力自己解决这个问题),但我故意省略了代码示例我已经尝试过,因为我相信必须有一个干净的解决方案来实现这一点,而且我不希望答案只是对我丑陋的非正统代码的小调整。

【问题讨论】:

    标签: validation angular


    【解决方案1】:

    我认为您正在寻找的是自定义表单控件。它可以完成您提到的所有事情,并大大减少了冗长。这是一个很大的主题,我不是专家,但这是一个很好的起点:Angular 2: Connect your custom control to ngModel with Control Value Accessor.

    示例解决方案:

    propertyEdit.component.ts:

    import {Component, DoCheck} from '@angular/core';
    import {TextInputComponent} from 'textInput.component';
    let validate = require('validate.js');
    
    @Component({
      selector: 'my-property-edit',
      template: `
        <my-text-input [(ngModel)]="property.name" label="Property Name" name="name" [errors]="errors['name']"></my-text-input>
        <my-text-input [(ngModel)]="property.address.address1" label="Address" name="address1" [errors]="errors['address.address1']></my-text-input>
      `,
      directives: [TextInputComponent],
    })
    export class PropertyEditComponent implements DoCheck {
    
      public property: any = {name: null, address: {address1: null}};
      public errors: any;
      public constraints: any = {
        name: {
          presence: true,
          length: {minimum: 3},
        },
        'address.address1': {
          presence: {message: "^Address can't be blank"},
          length: {minimum: 3, message: '^Address is too short (minimum is 3 characters)'},
        }
      };
    
      public ngDoCheck(): void {
        this.validate();
      }
    
      public validate(): void {
        this.errors = validate(this.property, this.constraints) || {};
      }
    }
    

    textInput.component.ts:

    import {Component, Input, forwardRef} from '@angular/core';
    import {ControlValueAccessor, NG_VALUE_ACCESSOR, NgModel} from '@angular/forms';
    
    const noop = (_?: any) => {};
    
    @Component({
      selector: 'my-text-input',
      template: `
        <div class="form-group" [class.has-error]="hasErrors(input)">
          <label class="control-label" [attr.for]="name">{{label}}</label>
          <input class="form-control" type="text" [name]="name" [(ngModel)]="value" #input="ngModel" [id]="name" />
          <div class="alert alert-danger" *ngIf="hasErrors(input)">{{errors}}</div>
        </div>
      `,
      providers: [
        { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TextInputComponent), multi: true },
      ],
    })
    export class TextInputComponent implements ControlValueAccessor {
    
      protected _value: any;
      protected onChange: (_: any) => void = noop;
      protected onTouched: () => void = noop;
    
      @Input() public label: string;
      @Input() public name: string;
      @Input() public errors: any;
    
      get value(): any {
        return this._value;
      }
    
      set value(value: any) {
        if (value !== this._value) {
          this._value = value;
          this.onChange(value);
        }
      }
    
      public writeValue(value: any) {
        if (value !== this._value) {
          this._value = value;
        }
      }
    
      public registerOnChange(fn: (_: any) => void) {
        this.onChange = fn;
      }
    
      public registerOnTouched(fn: () => void) {
        this.onTouched = fn;
      }
    
      public hasErrors(input: NgModel): boolean {
        return input.touched && this.errors != null;
      }
    }
    

    【讨论】:

    • 我只是在查看并实施该解决方案。感谢您的指点。它仍然感觉有点不正统,必须设置提供程序和 onChangeonTouched 回调,所以我会留下这个问题,希望有一个更清洁的解决方案,但到目前为止,这看起来有点有希望。跨度>
    • 我将我实施的解决方案添加到您的答案中,因为它是您建议的文章的相关实施,适合这个问题的需要。我仍然希望有另一种解决方案来帮助摆脱丑陋的提供程序和样板方法。
    猜你喜欢
    • 2018-04-22
    • 2016-07-23
    • 1970-01-01
    • 2016-02-02
    • 2012-09-05
    • 2021-04-30
    • 1970-01-01
    • 2013-02-09
    • 2022-12-11
    相关资源
    最近更新 更多