GVKun编程网logo

angular – 使用表单构建器的自定义表单控件属性(angular form表单)

10

本篇文章给大家谈谈angular–使用表单构建器的自定义表单控件属性,以及angularform表单的知识点,同时本文还将给你拓展Angular10:在AngularMaterial自定义表单字段控件

本篇文章给大家谈谈angular – 使用表单构建器的自定义表单控件属性,以及angular form表单的知识点,同时本文还将给你拓展Angular 10:在Angular Material自定义表单字段控件中访问NgControl、Angular 2:使用输入格式自定义表单验证、Angular 4.x 自定义表单控件、angular 5 自定义表单验证等相关知识,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

angular – 使用表单构建器的自定义表单控件属性(angular form表单)

angular – 使用表单构建器的自定义表单控件属性(angular form表单)

有没有办法使用Angular中的表单生成器添加名为filterMode的自定义属性?我正在创建一个搜索表单,但希望能够将过滤器类型绑定到它,如startsWith,contains,equal等.当我获得FormGroup控件对象时,我希望能够访问每个表单控件的这个值.

例如:

public queryForm: FormGroup;


constructor(
    private fb: FormBuilder,) {
    this.queryForm = this.fb.group({
      username: [value: '',filterMode:'contains'],email: [value: '',});
  }

有没有办法在Angular中扩展FormGroup?

解决方法

您需要使用值访问器创建组件.

编号:https://angular.io/api/forms/ControlValueAccessor

我们的想法是构建组件,实现值访问器和接口方法然后你可以使用该组件作为复杂的formgroup元素..像这样的东西:

constructor(
    private fb: FormBuilder,) {
    this.queryForm = this.fb.group({
      username: [value: ''],});
  }

你的用户名实际上是:

export class SomeType {
   username:string,filterMode:string
}

因此,基本上,您的表单的用户名字段变为复杂类型SomeType.

Angular 10:在Angular Material自定义表单字段控件中访问NgControl

Angular 10:在Angular Material自定义表单字段控件中访问NgControl

直接在构造函数中注入ngControl会导致循环依赖。 可以这样避免:

constructor(
    private injector: Injector,...
  ) {
    ...
  }

ngOnInit(): void {
    this.ngControl = this.injector.get(NgControl);
  }

Angular 2:使用输入格式自定义表单验证

Angular 2:使用输入格式自定义表单验证

我有一种情况,我需要格式化用户输入,然后验证它.

我正在使用反应式表单并创建我的自定义验证,如下所示(相关部分):

HTML:

<input type="text" formControlName="invoiceNumber" (blur)="formatInvoiceNumber()">
<div *ngIf="this.form.controls['invoiceNumber'].invalid && this.form.controls['invoiceNumber'].touched">Invalid Text</div>

控制器:

this.form = this.formBuilder.group({
            'invoiceNumber': ['',validateInvoiceNumber()],});

    formatRoNumber() {
            var invoiceNumber = this.form.controls['invoiceNumber'].value;
            //format invoice number
        }

验证器:

export function validateInvoiceNumber(): ValidatorFn {
    return (control: AbstractControl): { [key: string]: any } => {
        let invoiceNumber = control.value,isValid = true;

        //validate invoice number
        return isValid ? null : { validInvoiceNumber: { valid: false } }
    };
}

我遇到了计时问题.输入格式化在验证后发生.

如何告诉Angular应用格式然后验证?

解决方法

如果您还使用ngModel进行格式化,则可以使用formbuilder执行此操作.我已经完成了它

<ion-input
formControlName="fullName"
type="text"
[ngModel]="fullName | pipeHere"
(ngModelChange)="fullName=$event">
</ion-input>

this.customerFields = this.fb.group({
  fullName: ['',Validators.compose([ Validators.required,Validators.minLength(11) ])]
});

(ngModelChange)将像往常一样在验证时激活管道.如果您编写自定义管道,则还可以进行其他验证.

Angular 4.x 自定义表单控件

Angular 4.x 自定义表单控件

当我们打算自定义表单控件前,我们应该先考虑一下以下问题:

  • 是否已经有相同语义的 native (本机) 元素?如:<input type="number">

  • 如果有,我们就应该考虑能否依赖该元素,仅使用 CSS 或渐进增强的方式来改变其外观/行为就能满足我们的需求?

  • 如果没有,自定义控件会是什么样的?

  • 我们如何让它可以访问 (accessible)?

  • 在不同平台上自定义控件的行为是否有所不同?

  • 自定义控件如何实现数据验证功能?

可能还有很多事情需要考虑,但如果我们决定使用 Angular 创建自定义控件,就需要考虑以下问题:

  • 如何实现 model -> view 的数据绑定?

  • 如何实现 view -> model 的数据同步?

  • 若需要自定义验证,应该如何实现?

  • 如何向DOM元素添加有效性状态,便于设置不同样式?

  • 如何让控件可以访问 (accessible)?

  • 该控件能应用于 template-driven 表单?

  • 该控件能应用于 model-driven 表单?

(备注:主要浏览器上 HTML 5 当前辅助功能支持状态,可以参看 - HTML5 Accessibility)

Creating a custom counter

现在我们从最简单的 Counter 组件开始,具体代码如下:

counter.component.ts

import { Component, Input } from ''@angular/core'';

@Component({
    selector: ''exe-counter'',
    template: `
    <div>
      <p>当前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    </div>
    `
})
export class CounterComponent {
    @Input() count: number = 0;

    increment() {
        this.count++;
    }

    decrement() {
        this.count--;
    }
}

app.component.ts

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

@Component({
  selector: ''exe-app'',
  template: `
    <exe-counter></exe-counter>
  `,
})
export class AppComponent { }

app.module.ts

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from ''@angular/core'';
import { BrowserModule } from ''@angular/platform-browser'';

import { CounterComponent } from ''./couter.component'';
import { AppComponent } from ''./app.component'';

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent, CounterComponent],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

很好,CounterComponent 组件很快就实现了。但现在我们想在 Template-DrivenReactive 表单中使用该组件,具体如下:

<!-- this doesn''t work YET -->
<form #form="ngForm">
  <exe-counter name="counter" ngModel></exe-counter>
  <button type="submit">Submit</button>
</form>

现在我们还不能直接这么使用,要实现该功能。我们要先搞清楚 ControlValueAccessor,因为它是表单模型和DOM 元素之间的桥梁。

Understanding ControlValueAccessor

当我们运行上面示例时,浏览器控制台中将输出以下异常信息:

Uncaught (in promise): Error: No value accessor for form control with name: ''counter''

那么,ControlValueAccessor 是什么?那么你们还记得我们之前提到的实现自定义控件需要确认的事情么?其中一个要确认的事情就是,要实现 Model -> View,View -> Model 之间的数据绑定,而这就是我们 ControlValueAccessor 要处理的问题。

ControlValueAccessor 是一个接口,它的作用是:

  • 把 form 模型中值映射到视图中

  • 当视图发生变化时,通知 form directives 或 form controls

Angular 引入这个接口的原因是,不同的输入控件数据更新方式是不一样的。例如,对于我们常用的文本输入框来说,我们是设置它的 value 值,而对于复选框 (checkbox) 我们是设置它的 checked 属性。实际上,不同类型的输入控件都有一个 ControlValueAccessor,用来更新视图。

Angular 中常见的 ControlValueAccessor 有:

  • DefaultValueAccessor - 用于 texttextarea 类型的输入控件

  • SelectControlValueAccessor - 用于 select 选择控件

  • CheckboxControlValueAccessor - 用于 checkbox 复选控件

接下来我们的 CounterComponent 组件需要实现 ControlValueAccessor 接口,这样我们才能更新组件中 count 的值,并通知外界该值已发生改变。

Implementing ControlValueAccessor

首先我们先看一下 ControlValueAccessor 接口,具体如下:

// angular2/packages/forms/src/directives/control_value_accessor.ts 
export interface ControlValueAccessor {
  writeValue(obj: any): void;
  registerOnChange(fn: any): void;
  registerOnTouched(fn: any): void;
  setDisabledState?(isDisabled: boolean): void;
}
  • writeValue(obj: any):该方法用于将模型中的新值写入视图或 DOM 属性中。

  • registerOnChange(fn: any):设置当控件接收到 change 事件后,调用的函数

  • registerOnTouched(fn: any):设置当控件接收到 touched 事件后,调用的函数

  • setDisabledState?(isDisabled: boolean):当控件状态变成 DISABLED 或从 DISABLED 状态变化成 ENABLE 状态时,会调用该函数。该函数会根据参数值,启用或禁用指定的 DOM 元素。

接下来我们先来实现 writeValue() 方法:

@Component(...)
class CounterComponent implements ControlValueAccessor {
  ...
  writeValue(value: any) {
    this.counterValue = value;
  }
}

当表单初始化的时候,将会使用表单模型中对应的初始值作为参数,调用 writeValue() 方法。这意味着,它会覆盖默认值0,一切看来都没问题。但我们回想一下在表单中 CounterComponent 组件预期的使用方式:

<form #form="ngForm">
  <exe-counter name="counter" ngModel></exe-counter>
  <button type="submit">Submit</button>
</form>

你会发现,我们没有为 CounterComponent 组件设置初始值,因此我们要调整一下 writeValue() 中的代码,具体如下:

writeValue(value: any) {
  if (value) {
    this.count = value;
  }
}

现在,只有当合法值 (非 undefined、null、"") 写入控件时,它才会覆盖默认值。接下来,我们来实现 registerOnChange()registerOnTouched() 方法。registerOnChange() 可以用来通知外部,组件已经发生变化。registerOnChange() 方法接收一个 fn 参数,用于设置当控件接收到 change 事件后,调用的函数。而对于 registerOnTouched() 方法,它也支持一个 fn 参数,用于设置当控件接收到 touched 事件后,调用的函数。示例中我们不打算处理 touched 事件,因此 registerOnTouched() 我们设置为一个空函数。具体如下:

@Component(...)
class CounterComponent implements ControlValueAccessor {
  ...
  propagateChange = (_: any) => {};

  registerOnChange(fn: any) {
    this.propagateChange = fn;
  }

  registerOnTouched(fn: any) {}
}

很好,我们的 CounterComponent 组件已经实现了ControlValueAccessor 接口。接下来我们需要做的是在每次count 的值改变时,需要调用 propagateChange() 方法。换句话说,当用户点击了 +- 按钮时,我们希望将新值传递到外部。

@Component(...)
export class CounterComponent implements ControlValueAccessor {
    ...
    increment() {
        this.count++;
        this.propagateChange(this.count);
    }

    decrement() {
        this.count--;
        this.propagateChange(this.count);
    }
}

是不是感觉上面代码有点冗余,接下来我们来利用属性修改器,重构一下以上代码,具体如下:

counter.component.ts

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

@Component({
    selector: ''exe-counter'',
    template: `
      <p>当前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    `
})
export class CounterComponent implements ControlValueAccessor {
    @Input() _count: number = 0;

    get count() {
        return this._count;
    }

    set count(value: number) {
        this._count = value;
        this.propagateChange(this._count);
    }

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

    writeValue(value: any) {
        if (value !== undefined) {
            this.count = value;
        }
    }

    registerOnChange(fn: any) {
        this.propagateChange = fn;
    }

    registerOnTouched(fn: any) { }

    increment() {
        this.count++;
    }

    decrement() {
        this.count--;
    }
}

CounterComponent 组件已经基本开发好了,但要能正常使用的话,还需要执行注册操作。

Registering the ControlValueAccessor

对于我们开发的 CounterComponent 组件来说,实现 ControlValueAccessor 接口只完成了一半工作。要让 Angular 能够正常识别我们自定义的 ControlValueAccessor,我们还需要执行注册操作。具体方式如下:

  • 步骤一:创建 EXE_COUNTER_VALUE_ACCESSOR

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

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

友情提示:想了解 forwardRef 和 multi 的详细信息,请参考 Angular 2 Forward Reference 和 Angular 2 Multi Providers 这两篇文章。

  • 步骤二:设置组件的 providers 信息

@Component({
    selector: ''exe-counter'',
    ...
    providers: [EXE_COUNTER_VALUE_ACCESSOR]
})

万事俱备只欠东风,我们马上进入实战环节,实际检验一下我们开发的 CounterComponent 组件。完整代码如下:

counter.component.ts

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

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

@Component({
    selector: ''exe-counter'',
    template: `
    <div>
      <p>当前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    </div>
    `,
    providers: [EXE_COUNTER_VALUE_ACCESSOR]
})
export class CounterComponent implements ControlValueAccessor {
    @Input() _count: number = 0;

    get count() {
        return this._count;
    }

    set count(value: number) {
        this._count = value;
        this.propagateChange(this._count);
    }

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

    writeValue(value: any) {
        if (value) {
            this.count = value;
        }
    }

    registerOnChange(fn: any) {
        this.propagateChange = fn;
    }

    registerOnTouched(fn: any) { }

    increment() {
        this.count++;
    }

    decrement() {
        this.count--;
    }
}

Using it inside template-driven forms

Angular 4.x 中有两种表单:

  • Template-Driven Forms - 模板驱动式表单 (类似于 Angular 1.x 中的表单 )

  • Reactive Forms - 响应式表单

了解 Angular 4.x Template-Driven Forms 详细信息,请参考 - Angular 4.x Template-Driven Forms。接下来我们来看一下具体如何使用:

1.导入 FormsModule 模块

app.module.ts

import { FormsModule } from ''@angular/forms'';

@NgModule({
  imports: [BrowserModule, FormsModule],
  ...
})
export class AppModule { }

2.更新 AppComponent

2.1 未设置 CounterComponent 组件初始值

app.component.ts

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

@Component({
  selector: ''exe-app'',
  template: `
    <form #form="ngForm">
      <exe-counter name="counter" ngModel></exe-counter>
    </form>
    <pre>{{ form.value | json }}</pre>
  `,
})
export class AppComponent { }

友情提示:上面示例代码中,form.value 用于获取表单中的值,json 是 Angular 内置管道,用于执行对象序列化操作 (内部实现 - JSON.stringify(value, null, 2))。若想了解 Angular 管道详细信息,请参考 - Angular 2 Pipe。

2.2 设置 CounterComponent 组件初始值 - 使用 [ngModel] 语法

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

@Component({
  selector: ''exe-app'',
  template: `
    <form #form="ngForm">
      <exe-counter name="counter" [ngModel]="outerCounterValue"></exe-counter>
    </form>
    <pre>{{ form.value | json }}</pre>
  `,
})
export class AppComponent { 
  outerCounterValue: number = 5;  
}

2.3 设置数据双向绑定 - 使用 [(ngModel)] 语法

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

@Component({
  selector: ''exe-app'',
  template: `
    <form #form="ngForm">
      <p>outerCounterValue value: {{outerCounterValue}}</p>
      <exe-counter name="counter" [(ngModel)]="outerCounterValue"></exe-counter>
    </form>
    <pre>{{ form.value | json }}</pre>
  `,
})
export class AppComponent { 
  outerCounterValue: number = 5;  
}

Using it inside reactive forms

了解 Angular 4.x Reactive (Model-Driven) Forms 详细信息,请参考 - Angular 4.x Reactive Forms。接下来我们来看一下具体如何使用:

1.导入 ReactiveFormsModule

app.module.ts

import { ReactiveFormsModule } from ''@angular/forms'';

@NgModule({
  imports: [BrowserModule, ReactiveFormsModule],
  ...
})
export class AppModule { }

2.更新 AppComponent

import { Component, OnInit } from ''@angular/core'';
import { FormBuilder, FormGroup } from ''@angular/forms'';

@Component({
  selector: ''exe-app'',
  template: `
    <form [formGroup]="form">
      <exe-counter formControlName="counter"></exe-counter>
    </form>
    <pre>{{ form.value | json }}</pre>
  `,
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.form = this.fb.group({
      counter: 5 // 设置初始值
    });
  }
}

友情提示:上面代码中我们移除了 Template-Driven 表单中的 ngModel 和 name 属性,取而代之是使用 formControlName 属性。此外我们通过 FormBuilder 对象提供的 group() 方法,创建 FromGroup 对象,然后在模板中通过 [formGroup]="form" 的方式实现模型与 DOM 元素的绑定。关于 Reactive Forms 的详细信息,请参考 Angular 4.x Reactive Forms 。

最后我们在来看一下,如何为我们的自定义控件,添加验证规则。

Adding custom validation

在 Angular 4.x 基于AbstractControl自定义表单验证 这篇文章中,我们介绍了如何自定义表单验证。而对于我们自定义控件来说,添加自定义验证功能 (限制控件值的有效范围:0 <= value <=10),也很方便。具体示例如下:

1.自定义 VALIDATOR

1.1 定义验证函数

export const validateCounterRange: ValidatorFn = (control: AbstractControl): 
  ValidationErrors => {
    return (control.value > 10 || control.value < 0) ?
        { ''rangeError'': { current: control.value, max: 10, min: 0 } } : null;
};

1.2 注册自定义验证器

export const EXE_COUNTER_VALIDATOR = {
    provide: NG_VALIDATORS,
    useValue: validateCounterRange,
    multi: true
};

2.更新 AppComponent

接下来我们更新一下 AppComponent 组件,在组件模板中显示异常信息:

@Component({
  selector: ''exe-app'',
  template: `
    <form [formGroup]="form">
      <exe-counter formControlName="counter"></exe-counter>
    </form>
    <p *ngIf="!form.valid">Counter is invalid!</p>
    <pre>{{ form.get(''counter'').errors | json }}</pre>
  `,
})

CounterComponent 组件的完整代码如下:

counter.component.ts

import { Component, Input, forwardRef } from ''@angular/core'';
import {
    ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS,
    AbstractControl, ValidatorFn, ValidationErrors, FormControl
} from ''@angular/forms'';

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

export const validateCounterRange: ValidatorFn = (control: AbstractControl): 
  ValidationErrors => {
    return (control.value > 10 || control.value < 0) ?
        { ''rangeError'': { current: control.value, max: 10, min: 0 } } : null;
};

export const EXE_COUNTER_VALIDATOR = {
    provide: NG_VALIDATORS,
    useValue: validateCounterRange,
    multi: true
};

@Component({
    selector: ''exe-counter'',
    template: `
    <div>
      <p>当前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    </div>
    `,
    providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})
export class CounterComponent implements ControlValueAccessor {
    @Input() _count: number = 0;

    get count() {
        return this._count;
    }

    set count(value: number) {
        this._count = value;
        this.propagateChange(this._count);
    }

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

    writeValue(value: any) {
        if (value) {
            this.count = value;
        }
    }

    registerOnChange(fn: any) {
        this.propagateChange = fn;
    }

    registerOnTouched(fn: any) { }

    increment() {
        this.count++;
    }

    decrement() {
        this.count--;
    }
}

除了在 CounterComponent 组件的 Metadata 配置自定义验证器之外,我们也可以在创建 FormGroup 对象时,设置每个控件 (FormControl) 对象的验证规则。需调整的代码如下:

counter.component.ts

@Component({
    selector: ''exe-counter'',
    ...,
    providers: [EXE_COUNTER_VALUE_ACCESSOR] // 移除自定义EXE_COUNTER_VALIDATOR
})

app.component.ts

import { validateCounterRange } from ''./couter.component'';
...

export class AppComponent {
  ...
  ngOnInit() {
    this.form = this.fb.group({
      counter: [5, validateCounterRange] // 设置validateCounterRange验证器
    });
  }
}

自定义验证功能我们已经实现了,但验证规则即数据的有效范围是固定 (0 <= value <=10),实际上更好的方式是让用户能够灵活地配置数据的有效范围。接下来我们就来优化一下现有的功能,使得我们开发的组件更为灵活。

Making the validation configurable

我们自定义 CounterComponent 组件的预期使用方式如下:

<exe-counter
  formControlName="counter"
  counterRangeMax="10"
  counterRangeMin="0">
</exe-counter>

首先我们需要更新一下 CounterComponent 组件,增量 counterRangeMax 和 counterRangeMin 输入属性:

@Component(...)
class CounterInputComponent implements ControlValueAccessor {
  ...
  @Input() counterRangeMin: number;

  @Input() counterRangeMax: number;
  ...
}

接着我们需要新增一个 createCounterRangeValidator() 工厂函数,用于根据设置的最大值 (maxValue) 和最小值 (minValue) 动态的创建 validateCounterRange() 函数。具体示例如下:

export function createCounterRangeValidator(maxValue: number, minValue: number) {
    return (control: AbstractControl): ValidationErrors => {
        return (control.value > +maxValue || control.value < +minValue) ?
          { ''rangeError'': { current: control.value, max: maxValue, 
               min: minValue }} : null;
    }
}

在 Angular 4.x 自定义验证指令 文章中,我们介绍了如何自定义验证指令。要实现指令的自定义验证功能,我们需要实现 Validator 接口:

export interface Validator {
  validate(c: AbstractControl): ValidationErrors|null;
  registerOnValidatorChange?(fn: () => void): void;
}

另外我们应该在检测到 counterRangeMincounterRangeMax 输入属性时,就需要调用 createCounterRangeValidator() 方法,动态创建 validateCounterRange() 函数,然后在 validate() 方法中调用验证函数,并返回函数调用后的返回值。是不是有点绕,我们马上看一下具体代码:

import { Component, Input, OnChanges, SimpleChanges, forwardRef } from ''@angular/core'';
import {
    ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, Validator,
    AbstractControl, ValidatorFn, ValidationErrors, FormControl
} from ''@angular/forms'';

...

export const EXE_COUNTER_VALIDATOR = {
    provide: NG_VALIDATORS,
    useExisting: forwardRef(() => CounterComponent),
    multi: true
};

export function createCounterRangeValidator(maxValue: number, minValue: number) {
    return (control: AbstractControl): ValidationErrors => {
        return (control.value > +maxValue || control.value < +minValue) ?
            { ''rangeError'': { current: control.value, max: maxValue, min: minValue } } 
              : null;
    }
}

@Component({
    selector: ''exe-counter'',
    template: `
    <div>
      <p>当前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    </div>
    `,
    providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})
export class CounterComponent implements ControlValueAccessor, Validator,
    OnChanges {
    ...
    private _validator: ValidatorFn;
    private _onChange: () => void;

    @Input() counterRangeMin: number; // 设置数据有效范围的最大值

    @Input() counterRangeMax: number; // 设置数据有效范围的最小值

    // 监听输入属性变化,调用内部的_createValidator()方法,创建RangeValidator
    ngOnChanges(changes: SimpleChanges): void {
        if (''counterRangeMin'' in changes || ''counterRangeMax'' in changes) {
            this._createValidator();
        }
    }

    // 动态创建RangeValidator
    private _createValidator(): void {
        this._validator = createCounterRangeValidator(this.counterRangeMax,
           this.counterRangeMin);
    }

    // 执行控件验证
    validate(c: AbstractControl): ValidationErrors | null {
        return this.counterRangeMin == null || this.counterRangeMax == null ? 
            null : this._validator(c);
    }
      
  ...
}

上面的代码很长,我们来分解一下:

注册 Validator

export const EXE_COUNTER_VALIDATOR = {
    provide: NG_VALIDATORS,
    useExisting: forwardRef(() => CounterComponent),
    multi: true
};

@Component({
    selector: ''exe-counter'',
    ...,
    providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})

创建 createCounterRangeValidator() 工厂函数

export function createCounterRangeValidator(maxValue: number, minValue: number) {
    return (control: AbstractControl): ValidationErrors => {
        return (control.value > +maxValue || control.value < +minValue) ?
            { ''rangeError'': { current: control.value, max: maxValue, min: minValue } } 
              : null;
    }
}

实现 OnChanges 接口,监听输入属性变化创建RangeValidator

export class CounterComponent implements ControlValueAccessor, Validator,
    OnChanges {
    ...
    @Input() counterRangeMin: number; // 设置数据有效范围的最大值
    @Input() counterRangeMax: number; // 设置数据有效范围的最小值
    
    // 监听输入属性变化,调用内部的_createValidator()方法,创建RangeValidator
    ngOnChanges(changes: SimpleChanges): void {
        if (''counterRangeMin'' in changes || ''counterRangeMax'' in changes) {
            this._createValidator();
        }
    }
  ...
}

调用 _createValidator() 方法创建RangeValidator

export class CounterComponent implements ControlValueAccessor, Validator,
    OnChanges {
    ...
    // 动态创建RangeValidator
    private _createValidator(): void {
        this._validator = createCounterRangeValidator(this.counterRangeMax,
           this.counterRangeMin);
    }
  ...
}

实现 Validator 接口,实现控件验证功能

export class CounterComponent implements ControlValueAccessor, Validator,
    OnChanges {
    ...
    // 执行控件验证
    validate(c: AbstractControl): ValidationErrors | null {
        return this.counterRangeMin == null || this.counterRangeMax == null ? 
            null : this._validator(c);
    }
   ...
}

此时我们自定义 CounterComponent 组件终于开发完成了,就差功能验证了。具体的使用示例如下:

import { Component, OnInit } from ''@angular/core'';
import { FormBuilder, FormGroup } from ''@angular/forms'';

@Component({
  selector: ''exe-app'',
  template: `
    <form [formGroup]="form">
      <exe-counter formControlName="counter" 
        counterRangeMin="5" 
        counterRangeMax="8">
      </exe-counter>
    </form>
    <p *ngIf="!form.valid">Counter is invalid!</p>
    <pre>{{ form.get(''counter'').errors | json }}</pre>
  `,
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.form = this.fb.group({
      counter: 5
    });
  }
}

以上代码成功运行后,浏览器页面的显示结果如下:

图片描述

参考资源

  • thoughtram.io - CUSTOM FORM CONTROLS IN ANGULAR

angular 5 自定义表单验证

angular 5 自定义表单验证

本文参考地址:链接

第三方 UI 组件:Ant-Design

近期在做 angular 的表单,目前项目上已经升级到 angular 5 了,需要做一些自定义表单验证,用户输入的是在服务器上的绝对路径,所以要验证是否是以 “/” 开头

表单验证 (Validator) 的本质上是一个 function,感觉跟 pipe 思路类似

html:

<form nz-form [formGroup]="configForm" (ngSubmit)="submitConfig()" (ngReset)="resetConfig()">
    <div nz-form-item nz-row>
            <div nz-form-label nz-col [nzSpan]="6">
              <label nz-form-item-required>数据存储目录</label>
            </div>
            <div nz-form-control nz-col [nzSpan]="12" nzHasFeedback>
              <nz-input formControlName="NODE_PATH" [nzPlaceHolder]="''存储路径''" [nzSize]="''large''">
                <ng-template #addOnBefore>{{ filePathPrefix }}</ng-template>
              </nz-input>
              <div nz-form-explain *ngIf="validateCorrect(''NODE_PATH'')">
                只支持一个目录,比如:/home/temp/data
              </div>
              <div nz-form-explain *ngIf="validateError(''NODE_PATH'')">
                存储目录不可为空
              </div>
              <div nz-form-explain *ngIf="getFormControl(''NODE_PATH'').hasError(''invalidPath'')">
                绝对路径必须要以“/”开头
              </div>
            </div>
    </div>
</form>

typescript:

export class ConfigComponent implements OnInit {

  configForm : FormGroup;
  filePathPrefix = ''file://'';

  constructor(private fb: FormBuilder) {
  }

  ngOnInit() {
    this.configForm = this.fb.group({
      NODE_PATH: [null, Validators.required, ConfigComponent.validateFilePath]
    });
  
  }

  static validateFilePath(control: FormControl) {
    let filePath = control.value;
    return Observable.of(filePath && filePath.startsWith(''/'')).map(result => {
      return !!result ? null : { invalidPath: true };
    });
  }


  /**
   * 提交配置信息
   */
  submitConfig() {
    
  }

  /**
   * 重新配置信息
   */
  resetConfig() {
    
  }

  getFormControl(name) {
    return this.configForm.controls[name];
  }

}

validators 必须要返回一个 Observable 对象,因为是异步验证

当用户输入是以 “/” 开头时,返回 null,表示验证通过

当用户输入没有以 “/” 开头时,返回一个对象,表示验证失败,返回对象可以自定义,比如这里就返回了一个对象 { invalidPath: true } ,这样的话就可以在 html 里使用 hasError (''invalidPath'') 来取值了

这样写其实 html 冗余代码比较多,可以把各种提示信息再封装一下,让 html 更加简洁,可读性更强

关于angular – 使用表单构建器的自定义表单控件属性angular form表单的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于Angular 10:在Angular Material自定义表单字段控件中访问NgControl、Angular 2:使用输入格式自定义表单验证、Angular 4.x 自定义表单控件、angular 5 自定义表单验证的相关知识,请在本站寻找。

本文标签: