首页 > 其他分享 >angular - 表单

angular - 表单

时间:2024-05-19 22:20:05浏览次数:29  
标签:控件 FormControl 验证 表单 new FormGroup angular

(1)模板驱动表单

参考:

简单表单

​ngForm​ 固定的,需要提交的值 ngModel​

 <form #sub="ngForm" (submit)="onSubmit(sub)">
        <input type="text" name="username" ngModel>  
        <input type="id" name="userId" ngModel>
        <button>提交</button>
      </form>
  onSubmit(form:NgForm){
    console.log(form.value);
    console.log(form.valid);

  }

分组

​ngModelGroup​ : 会创建 FormGroup 的实例并把它绑定到 DOM 元素中。

<form #sub="ngForm" (submit)="onSubmit(sub)">
        <ng-container ngModelGroup="user">
          <input type="text" name="username" ngModel>  
        </ng-container>
        <ng-container ngModelGroup="Guid">
        <input type="id" name="Id" ngModel>
      </ng-container>
        <button>提交</button>
      </form>

(2)模板驱动表单验证

Angular 内置表单验证器:

  1. required:检查输入字段是否为空。
  2. minlength:检查输入字段的长度是否符合最小长度要求。
  3. maxlength:检查输入字段的长度是否符合最大长度要求。
  4. min:检查输入字段的值是否符合最小值要求。
  5. max:检查输入字段的值是否符合最大值要求。
  6. pattern:使用正则表达式来验证输入字段的值是否符合特定的模式。
  7. email:检查输入字段的值是否符合电子邮件格式。
  8. url:检查输入字段的值是否符合 URL 格式。
  9. date:检查输入字段的值是否符合日期格式。
  10. checkbox:检查是否选择了复选框。
  11. number:检查输入字段的值是否为数字。
  12. ​requiredTrue()​: 这个验证器是 Angular 中的一个内置验证器,它用于检查输入字段是否被选中和/或填写。如果输入字段的值是 true​ 或者用户已经填写了该字段,那么这个验证就会通过。如果输入字段为空或者未被选中,那么这个验证就会失败。
  13. ​compose()​: 这个函数是用来组合多个验证器的。你可以将多个验证器传递给 compose()​ 函数,然后它将返回一个新的验证器,这个新的验证器将按照你指定的顺序执行这些验证器。你可以使用 pipe()​ 函数来将多个验证器串联起来,但是 compose()​ 更加灵活,因为它允许你在不同的上下文中重复使用相同的验证器。s

Angular 中文文档-内置验证器,可用于各种表单控件

显示未通过验证的信息

要启用验证功能:必须有一个模板引用变量 #username="ngModel"​,这里的 ngModel​指令提供的功能,用于验证状态。

验证对象属性

名称 描述
path 这个属性返回元素的名称
valid 通过验证规则定义的条件 (样式 ng-valid)
invalid 不能通过验证规则定义的条件 (样式 ng-invalid)
pristine 内容没修改,未被用户编辑(样式 ng-pristine)
dirty 内容被修改,被用户编辑 (样式 ng-drity)
touched 元素被用户访问 (样式 ng-touched)(一般通过制表符 TAB​选择表单域)
untouched 元素未被用户访问, (样式 ng-untouched)(一般通过制表符 TAB​选择表单域)
errors 返回一个 ValidationErrors 键值对对象
​export declare type ValidationErrors = {

​ [key: string]: any;

​};
value 返回 value 值,用于 自定义表单验证规则​
      <form #sub="ngForm" (submit)="onSubmit(sub)">
        <input type="text" name="username" required pattern="\d"  #username="ngModel" as ngModel>
        <div *ngIf="username.touched && username.invalid && username.errors">
          <div *ngIf="username.errors['required']">必须填写</div>
          <div *ngIf="username.errors['pattern']">必须为数字</div>
        </div>

        <input type="id" name="userId" ngModel>
        <button  [disabled]="sub.invalid">提交</button>
      </form>

这里 angular 15 使用的 errors["XXXX"]​ 的方式(ValidationErrors​),angular 12 使用的 errors.requered 的方式。

表单验证错误描述属性

名称 描述
minlength.requiredLength 所需的字符数
minlength.actualLength 输入的字符数
pattern.requiredPattern 返回指定正则
pattern.actualValue 元素的内容

使用组件显示验证消息,验证整个表单

formSubmitted 指定表单是否已经提交,并在提交前阻止验证。

component.ts

import { ApplicationRef, Component } from '@angular/core';
import { Model } from './repository.model';
import { Product } from './product.model';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app',
  templateUrl: 'template.html',
})
export class ProductComponent {
  model: Model = new Model();
  //是否已经提交
  formSubmitted: boolean = false;

  submitForm(form: NgForm) {
    this.formSubmitted = true;
    if (form.valid) {
      this.addProject(this.newProduct);
      this.newProduct = new Product();
      form.reset();
      this.formSubmitted = false;
    }
  }

  getValidationMessages(state: any, thingName?: string) {
    let thing: string = state.path || thingName;
    let messages: string[] = [];
    if (state.errors) {
      for (const errorName in state.errors) {
        switch (errorName) {
          case 'required':
            messages.push(`you must enter a ${thing}`);
            break;
          case 'minlength':
            messages.push(
              `a ${thing} must be at least ${state.errors['minlength'].requiredLength}`
            );
            break;
          case 'pattern':
            messages.push(`The ${thing} contains illegal chracters`);
            break;
        }
      }
    }

    return messages;
  } 

//......other method
}

html

form定义一个引用变量 form​,ngForm​赋值给它:#form="ngForm"​,ngSubmit 绑定表达式调用控制器 submitForm​

​name="name"​这里的字符串name就是 #username​的 path​

<!-- 14.4.3 验证整个表单 -->
<style>
  input.ng-dirty.ng-invalid {
    border: 2px solid #ff0000
  }

  input.ng-dirty.ng-valid {
    border: 2px solid #6bc502
  }
</style>

<div class="m-2">
  <div class="bg-info text-white mb-2 p-2">Model Data:{{jsonProduct}}</div>

  <form #form="ngForm" (ngSubmit)="submitForm(form)">
    <div class="bg-danger text-white p-2 mb-2" *ngIf="formSubmitted && form.invalid">
      There are problems with the form
    </div>
    <div class="form-group">
      <label>Name</label>
     <input class="form-control" name="name" [(ngModel)]="newProduct.name" #username="ngModel" required minlength="5"
        pattern="^[A-Za-z ]+$" />
      <ul class="text-danger list-unstlyed"
        *ngIf="(formSubmitted || username.dirty) && username.invalid && username.errors">
        <li *ngFor="let error of getValidationMessages(username)">
          <span>{{error}}</span>
        </li>
      </ul>
    </div>

    <button class="btn btn-primary" type="submit">Create</button>
  </form>

</div>

1 显示摘要信息

component.ts

通过 NgForm​ 对象的 controls​ 属性访问各个元素

  getFormValidationMessages(form: NgForm): string[] {
    let messages: string[] = [];
    Object.keys(form.controls).forEach((k) => {
      this.getValidationMessages(form.controls[k], k).forEach((m) =>
        messages.push(m)
      );
    });
    return messages;
  }

html

<!-- 14.4.3-1 显示验证摘要信息 -->
<style>
  input.ng-dirty.ng-invalid {
    border: 2px solid #ff0000
  }

  input.ng-dirty.ng-valid {
    border: 2px solid #6bc502
  }
</style>

<div class="m-2">
  <div class="bg-info text-white mb-2 p-2">Model Data:{{jsonProduct}}</div>

  <form #form="ngForm" (ngSubmit)="submitForm(form)">
    <div class="bg-danger text-white p-2 mb-2" *ngIf="formSubmitted && form.invalid">
      There are problems with the form
      <ul>
        <li *ngFor="let error of getFormValidationMessages(form)">
          {{error}}
        </li>
      </ul>
    </div>

    <div class="form-group">
      <label>Name</label>
      <input class="form-control" name="name" [(ngModel)]="newProduct.name" #username="ngModel" required minlength="5"
        pattern="^[A-Za-z ]+$" />
      <ul class="text-danger list-unstlyed"
        *ngIf="(formSubmitted || username.dirty) && username.invalid && username.errors">
        <li *ngFor="let error of getValidationMessages(username)">
          <span>{{error}}</span>
        </li>
      </ul>
    </div>

    <button class="btn btn-primary" type="submit">Create</button>
  </form>

</div>

2 禁用提交按钮

<button class="btn btn-primary" type="submit" [disabled]="formSubmitted && form.invalid"
      [class.btn-secondary]="formSubmitted && form.invalid">
      Create
    </button>

(3)模型驱动表单

3.1) 响应式表单

3.1.1) 创建基础表单

1 在你的应用中注册响应式表单模块。该模块声明了一些你要用在响应式表单中的指令。

要使用响应式表单控件,就要从 @angular/forms 包中导入 ReactiveFormsModule,并把它添加到你的 NgModule 的 imports 数组中。

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

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

2 生成一个新的 FormControl 实例,并把它保存在组件中。

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-name-editor',
  templateUrl: './name-editor.component.html',
  styleUrls: ['./name-editor.component.css']
})
export class NameEditorComponent {
  name = new FormControl('');
}

3 在模板中注册这个 FormControl。

<label for="name">Name: </label>
<input id="name" type="text" [formControl]="name">

使用这种模板绑定语法,把该表单控件注册给了模板中名为 name 的输入元素。这样,表单控件和 DOM 元素就可以互相通讯了:视图会反映模型的变化,模型也会反映视图中的变化。

a) 显示表单值

  • 通过可观察对象 valueChanges,你可以在模板中使用 AsyncPipe或在组件类中使用 subscribe() 方法来监听表单值的变化。

  • 使用 value 属性。它能让你获得当前值的一份快照。

<p>Value: {{ name1.value }}</p>

<label for="name">Name: </label>
<input id="name" type="text" [formControl]="name1">

一旦你修改了表单控件所关联的元素,这里显示的值也跟着变化了。

b) 替换表单控件的值

html

<p>Value: {{ name1.value }}</p>

<label for="name">Name: </label>
<input id="name" type="text" [formControl]="name1">
<button type="button" (click)="updateName()">Update Name</button>

FormControl 实例提供了一个 setValue() 方法,它会修改这个表单控件的值

  updateName() {
    this.name1.setValue('Nancy');
  }

3.1.2) 把表单分组 FormGroup 和 FromArray

FormGroup

定义了一个带有一组控件的表单,你可以把它们放在一起管理。表单组的基础知识将在本节中讨论。你也可以通过嵌套表单组来创建更复杂的表单。

FormArray

定义了一个动态表单,你可以在运行时添加和删除控件。你也可以通过嵌套表单数组来创建更复杂的表单。欲知详情,参阅下面的创建动态表单

a) FormGroup 创建确定子控件数量的动态表单

1.创建一个 FormGroup 实例。

要初始化这个 FormGroup,请为构造函数提供一个由控件组成的对象,对象中的每个名字都要和表单控件的名字一一对应

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

@Component({
  selector: 'app-ProfileEditor',
  templateUrl: './ProfileEditor.component.html',
  styleUrls: ['./ProfileEditor.component.css']
})
export class ProfileEditorComponent implements OnInit {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
  constructor() { }

  ngOnInit() {
  }
}

2 把这个 FormGroup 模型关联到视图

由 FormControlName 指令提供的 formControlName 属性把每个输入框和 FormGroup 中定义的表单控件绑定起来。这些表单控件会和相应的元素通讯,它们还把更改传给 FormGroup,这个 FormGroup 是模型值的事实之源。

<form [formGroup]="profileForm">

  <label for="first-name">First Name: </label>
  <input id="first-name" type="text" formControlName="firstName">

  <label for="last-name">Last Name: </label>
  <input id="last-name" type="text" formControlName="lastName">

</form>

3 保存表单数据。

ProfileEditor 组件从用户那里获得输入,但在真实的场景中,你可能想要先捕获表单的值,等将来在组件外部进行处理。FormGroup 指令会监听 form 元素发出的 submit 事件,并发出一个 ngSubmit 事件,让你可以绑定一个回调函数。把 onSubmit() 回调方法添加为 form 标签上的 ngSubmit 事件监听器。

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">

ProfileEditor 组件上的 onSubmit() 方法会捕获 profileForm 的当前值。要保持该表单的封装性,就要使用 EventEmitter 向组件外部提供该表单的值。下面的例子会使用 console.warn 把这个值记录到浏览器的控制台中。

onSubmit() {
  // TODO: Use EventEmitter with form value
  console.warn(this.profileForm.value);
}

form 标签所发出的 submit 事件是内置 DOM 事件,通过点击类型为 submit 的按钮可以触发本事件。这还让用户可以用回车键来提交填完的表单。往表单的底部添加一个 button,用于触发表单提交。

  <p>Complete the form to enable button.</p>
  <button type="submit" [disabled]="!profileForm.valid">Submit</button>

b) 嵌套表单组

1 创建一个嵌套的表单组

在这个例子中,address group 把现有的 firstNamelastName 控件和新的 streetcitystate 和 zip 控件组合在一起。虽然 address 这个 FormGroup 是 profileForm 这个整体 FormGroup 的一个子控件,但是仍然适用同样的值和状态的变更规则。来自内嵌控件组的状态和值的变更将会冒泡到它的父控件组,以维护整体模型的一致性

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

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    })
  });
}

2 在模板中对这个嵌套表单分组

内嵌的使用formGroupName

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">

  <label for="first-name">First Name: </label>
  <input id="first-name" type="text" formControlName="firstName">

  <label for="last-name">Last Name: </label>
  <input id="last-name" type="text" formControlName="lastName">

  <div formGroupName="address">
    <h2>Address</h2>

    <label for="street">Street: </label>
    <input id="street" type="text" formControlName="street">

    <label for="city">City: </label>
    <input id="city" type="text" formControlName="city">

    <label for="state">State: </label>
    <input id="state" type="text" formControlName="state">

    <label for="zip">Zip Code: </label>
    <input id="zip" type="text" formControlName="zip">
  </div>

  <p>Complete the form to enable button.</p>
  <button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>

c) 更新部分数据模型

当修改包含多个 FormGroup 实例的值时,你可能只希望更新模型中的一部分,而不是完全替换掉

方法 详情
setValue() 使用 setValue() 方法来为单个控件设置新值。setValue() 方法会严格遵循表单组的结构,并整体性替换控件的值。
patchValue() 用此对象中定义的任意属性对表单模型进行替换。

setValue() 方法的严格检查可以帮助你捕获复杂表单嵌套中的错误,而 patchValue() 在遇到那些错误时可能会默默的失败。

在 ProfileEditorComponent 中,使用 updateProfile 方法传入下列数据可以更新用户的名字与街道住址。

  updateProfile() {
    const newFirstName = 'Alice'; // 新的first name值
    // setValue 更新单个
    this.profileForm.get('firstName')?.setValue(newFirstName);

    // patchValue 更新多个一部分
    // this.profileForm.patchValue({
    //   firstName: 'Nancy',
    //   address: {
    //     street: '123 Drew Street'
    //   }
    // });
  }

加个按钮来更新

<button type="button" (click)="updateProfile()">Update Profile</button>

3.1.3) FormBuilder 服务生成控件

a 导入 FormBuilder 类。

b 注入这个 FormBuilder 服务。

c 生成表单内容。

FormBuilder 服务有三个方法:control()group() 和 array()。这些方法都是工厂方法,用于在组件类中分别生成 FormControlFormGroup 和 FormArray

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

@Component({
  selector: 'app-ProfileEditor',
  templateUrl: './ProfileEditor.component.html',
  styleUrls: ['./ProfileEditor.component.css']
})
export class ProfileEditorComponent implements OnInit {
  profileForm = this.fb.group({
    firstName: [''],
    lastName: [''],
    address: this.fb.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
  });

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
  }
}
// ts{10-19,21}

在上面的例子中,你可以使用 group() 方法,用和前面一样的名字来定义这些属性。这里,每个控件名对应的值都是一个数组,这个数组中的第一项是其初始值。

3.1.4) 响应式表单的验证

3.1.4.1) 在表单组件中导入一个验证器函数。

从 @angular/forms 包中导入 Validators 类。

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

3.1.4.2) 把这个验证器添加到表单中的相应字段。

profileForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
});

3.1.4.3) 添加逻辑来处理验证状态。

<p>Form Status: {{ profileForm.status }}</p>

参考:

3.1.5) FormArray 创建不确定数量子控件的动态表单

FormArray 如果你事先不知道子控件的数量,这就是一个很好的选择。

FormArray 是 FormGroup 之外的另一个选择,用于管理任意数量的匿名控件。像 FormGroup 实例一样,你也可以往 FormArray 中动态插入和移除控件,并且 FormArray 实例的值和验证状态也是根据它的子控件计算得来的。不过,你不需要为每个控件定义一个名字作为 key.

a) 导入 FormArray 类。

b) 定义一个 FormArray 控件。

使用 FormBuilder.array() 方法来定义该数组,并用 FormBuilder.control() 方法来往该数组中添加一个初始控件。

FormGroup 中的这个 aliases 控件现在管理着一个控件,将来还可以动态添加多个。

profileForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
  aliases: this.fb.array([
    this.fb.control('')
  ])
});

c) 使用 getter 方法访问 FormArray 控件。

相对于重复使用 profileForm.get() 方法获取每个实例的方式,getter 可以让你轻松访问表单数组各个实例中的别名。表单数组实例用一个数组来代表未定数量的控件。通过 getter 来访问控件很方便,这种方法还能很容易地重复处理更多控件。
使用 getter 语法创建类属性 aliases,以从父表单组中接收表示绰号的表单数组控件。

get aliases() {
  return this.profileForm.get('aliases') as FormArray;
}

注意
因为返回的控件的类型是 AbstractControl,所以你要为该方法提供一个显式的类型声明来访问 FormArray 特有的语法。

定义一个方法来把一个绰号控件动态插入到绰号 FormArray 中。用 FormArray.push() 方法把该控件添加为数组中的新条目。

addAlias() {
  this.aliases.push(this.fb.control(''));
}

在这个模板中,这些控件会被迭代,把每个控件都显示为一个独立的输入框。

d) 在模板中显示这个表单数组。

要想为表单模型添加 aliases,你必须把它加入到模板中供用户输入。和 FormGroupNameDirective 提供的 formGroupName 一样,FormArrayNameDirective 也使用 formArrayName 在这个 FormArray 实例和模板之间建立绑定。
在 formGroupName <div> 元素的结束标签下方,添加一段模板 HTML。

  <div formArrayName="aliases">
    <h2>Aliases</h2>
    <button type="button" (click)="addAlias()">+ Add another alias</button>

    <div *ngFor="let alias of aliases.controls; let i=index">
      <!-- The repeated alias template -->
      <label for="alias-{{ i }}">Alias:</label>
      <input id="alias-{{ i }}" type="text" [formControlName]="i">
    </div>
  </div>

3.2) 严格类型化表单

从 Angular 14 开始,响应式表单默认是严格类型的。

3.2.1) FormControl 入门

const email = new FormControl('[email protected]');

此控件将被自动推断为 FormControl<string|null> 类型。TypeScript 会在整个FormControl API中自动强制执行此类型,例如 email.value 、 email.valueChanges 、 email.setValue(...) 等。

a) 可空性 nonNullable

你可能想知道:为什么此控件的类型包含 null ?这是因为控件可以随时通过调用 reset 变为 null

const email = new FormControl('[email protected]');
email.reset();
console.log(email.value); // null

TypeScript 将强制你始终处理控件已变为 null 的可能性。如果要使此控件不可为空,可以用 nonNullable 选项。这将导致控件重置为其初始值,而不是 null

const email = new FormControl('[email protected]', {nonNullable: true});
email.reset();
console.log(email.value); // [email protected]

3.2.2) FormArray:动态的、同质的集合

FormArray 包含一个开放式控件列表。type 参数对应于每个内部控件的类型:

const names = new FormArray([new FormControl('Alex')]);
names.push(new FormControl('Jess'));

此 FormArray 将具有内部控件类型 FormControl<string|null>

如果你想在数组中有多个不同的元素类型,则必须使用 UntypedFormArray,因为 TypeScript 无法推断哪种元素类型将出现在哪个位置。

3.2.3) FormGroup 和 FormRecord

Angular 为具有枚举键集的表单提供了 FormGroup 类型,并为开放式或动态组提供了一种名为 FormRecord 的类型。

a) 部分值

const login = new FormGroup({
    email: new FormControl('', {nonNullable: true}),
    password: new FormControl('', {nonNullable: true}),
});

在任何 FormGroup 上,都可以禁用控件。任何禁用的控件都不会出现在组的值中。

因此,login.value 的类型是 Partial<{email: string, password: string}>。这种类型的 Partial 意味着每个成员可能是未定义的。

更具体地说,login.value.email 的类型是 string|undefined,TypeScript 将强制你处理可能 undefined 的值(如果你启用了 strictNullChecks)。

如果你想访问包括禁用控件的值,从而绕过可能的 undefined 字段,可以用 login.getRawValue()

b) 可选控件和动态组

某些表单的控件可能存在也可能不存在,可以在运行时添加和删除。你可以用可选字段来表示这些控件:

interface LoginForm {
    email: FormControl<string>;
    password?: FormControl<string>;
}

const login = new FormGroup<LoginForm>({
    email: new FormControl('', {nonNullable: true}),
    password: new FormControl('', {nonNullable: true}),
});

login.removeControl('password');

在这个表单中,我们明确地指定了类型,这使我们可以将 password 控件设为可选的。TypeScript 会强制只有可选控件才能被添加或删除。

c) FormRecord

某些 FormGroup 的用法不符合上述模式,因为键是无法提前知道的。FormRecord 类就是为这种情况设计的:

const addresses = new FormRecord<FormControl<string|null>>({});
addresses.addControl('Andrew', new FormControl('2340 Folsom St'));

任何 string|null 类型的控件都可以添加到此 FormRecord

如果你需要一个动态(开放式)和异构(控件是不同类型)的 FormGroup,则无法提升为类型安全的,这时你应该使用 UntypedFormGroup

FormRecord 也可以用 FormBuilder 构建:

const addresses = fb.record({'Andrew': '2340 Folsom St'});

如果你需要一个动态(开放式)和异构(控件是不同类型)的 FormGroup,则无法提高类型安全,你应该使用 UntypedFormGroup

3.2.4) FormBuilder 和 NonNullableFormBuilder

FormBuilder 类已升级为支持新增的类型的版本,方式与上面的示例相同。

此外,还有一个额外的构建器:NonNullableFormBuilder。它是在所有控件都上指定 {nonNullable: true} 的简写,用来在大型非空表单中消除主要的样板代码。你可以用 FormBuilder 上的 nonNullable 属性访问它:

const fb = new FormBuilder();
const login = fb.nonNullable.group({
    email: '',
    password: '',
});

在上面的示例中,两个内部控件都将不可为空(即将设置 nonNullable)。

你还可以用名称 NonNullableFormBuilder 注入它。

3.3) 构建动态表单

3.4) 例子

ReactiveFormsModule 的使用

app.module.ts

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

// import { AppComponent } from './app.component';
import { ProductComponent } from './component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [ProductComponent], //列出应用的类,告诉Angular哪些类构成了这个模块。
  imports: [BrowserModule, FormsModule, ReactiveFormsModule],
  providers: [], //提供服务
  bootstrap: [ProductComponent], //根组件
})
export class AppModule {}

基于模型的功能在 ReactiveFormsModule 模块定义中。

FormControl 和 FormGroup 的使用

FormControl: 表单中的表单项

FormGroup: 表单组,表单至少是一个 FormGroup

// form.module.ts
import { FormControl, FormGroup, Validators } from '@angular/forms';

export class ProductFormControl extends FormControl {   //FormControl表单中的单个元素
  lable: string;  
  modelProperty: string;

  /**
   * 构造函数用于初始化一个带有label、模型属性、值和验证器的 FormControl。
   * @param {string} lable - 表示控件的标签或名称的字符串。它用于在用户界面中显示控件的label。
   * @param {string} modelProperty - 表示绑定到此表单控件的模型对象的属性名称的字符串。它用于将表单控件的值映射到模型对象的相应属性上。
   * @param {any} value - 代表控件当前值的参数。对于 FormControl,它是当前值。对于已启用的 FormGroup,它是组内所有已启用控件的值,以键值对的形式呈现为对象。对于已禁用的 FormGroup,它是组内所有控件的值。
   * @param {any} validator - 用于确定控件有效性的函数。它可以是一个单独的验证器函数,也可以是使用 Validators.compose() 函数组合多个验证器函数的结果。验证器用于对控件的值执行验证检查,并在值无效时返回错误对象。
   */
  constructor(
    lable: string,
    modelProperty: string,
    value: any,
    validator: any
  ) {

/**
 * 例子:const fc = new FormControl('foo');
 *  
 * value: 控件的当前值。
    对于 FormControl,是当前值。
    对于已启用的 FormGroup,是组内所有已启用控件的值,以对象形式呈现,每个组成员都有一个键值对。
    对于已禁用的 FormGroup,是组内所有控件的值,以对象形式呈现,每个组成员都有一个键值对。
    对于 FormArray,是组内所有已启用控件的值,以数组形式呈现。

    validator: 返回用于同步确定此控件有效性的函数。如果已添加多个验证器,则这将是一个组合函数。有关更多信息,请参阅 Validators.compose()。
 */  
    super(value, validator);  //实现 FormControl 的构造函数
    this.lable = lable;
    this.modelProperty = modelProperty;
  }
}

export class ProductFormGroup extends FormGroup {   //FormGroup 管理form 元素
  constructor() {
    super({
      name: new ProductFormControl('Name', 'name', '', Validators.required),
      category: new ProductFormControl(
        'Category',
        'category',
        '',
        Validators.compose([
          Validators.required,
          Validators.pattern('^[A-Za-z ]+$'),
          Validators.minLength(3),
          Validators.maxLength(10),
        ])
      ),
      price: new ProductFormControl(
        'Price',
        'price',
        '',
        Validators.compose([
          Validators.required,
          Validators.pattern('^[0-9\.]+$'),
        ])
      ),
    });
  }
}

​FormGroup​​的构造函数接收一个对象,该对象各个属性与各个属性的名称与模板各个 input 元素的名称一一对应,每个属性都赋予一个用来表示该 input 元素的 ProductFormControl​​ 对象,该对象同时指定 input 的验证要求。

传给超类构造函数对象第一个属性最简单:

​name: new ProductFormControl('Name', 'name', '', Validators.required),​​

属性名为 name​​,告诉angular 属性对应模板中名为 name 的input元素。ProductFormControl 构造函数实参指定:

与 input 元素相关的 label 元素内容(Name),

input 元素绑定的 Product 类的某个属性名称(name),

数据绑定的初始值(空字符串)

所需的验证(Validators.required)

可以使用 Validators.compose​​ 将多个验证器组合起来。

FormArray: 复杂表单, 动态添加表单项和表单组,表单验证时,FormArray 有一项没通过,整体不通过。

(4)模型驱动的表单验证

错误消息生成从组件移动到表单模型类,让组件尽可能简单。ProductFormControl​​ 类的 getValidationMessages​​ 方法中尉 maxLength​​ 添加的验证消息。

// form.module.ts
import { FormControl, FormGroup, Validators } from '@angular/forms';

export class ProductFormControl extends FormControl {
  //FormControl表单中的单个元素
  lable: string;
  modelProperty: string;

  /**
   * 构造函数用于初始化一个带有lable、模型属性、值和验证器的 FormControl。
   * @param {string} lable - 表示控件的标签或名称的字符串。它用于在用户界面中显示控件的 lable。
   * @param {string} modelProperty - 表示绑定到此表单控件的模型对象的属性名称的字符串。它用于将表单控件的值映射到模型对象的相应属性上。
   * @param {any} value - 代表控件当前值的参数。对于 FormControl,它是当前值。对于已启用的 FormGroup,它是组内所有已启用控件的值,以键值对的形式呈现为对象。对于已禁用的 FormGroup,它是组内所有控件的值。
   * @param {any} validator - 用于确定控件有效性的函数。它可以是一个单独的验证器函数,也可以是使用 Validators.compose() 函数组合多个验证器函数的结果。验证器用于对控件的值执行验证检查,并在值无效时返回错误对象。
   */
  constructor(
    lable: string,
    modelProperty: string,
    value: any,
    validator: any
  ) {
    /**
 * 例子:const fc = new FormControl('foo');
 *  
 * value: 控件的当前值。
    对于 FormControl,是当前值。
    对于已启用的 FormGroup,是组内所有已启用控件的值,以对象形式呈现,每个组成员都有一个键值对。
    对于已禁用的 FormGroup,是组内所有控件的值,以对象形式呈现,每个组成员都有一个键值对。
    对于 FormArray,是组内所有已启用控件的值,以数组形式呈现。

    validator: 返回用于同步确定此控件有效性的函数。如果已添加多个验证器,则这将是一个组合函数。有关更多信息,请参阅 Validators.compose()。
 */
    super(value, validator); //实现 FormControl 的构造函数
    this.lable = lable;
    this.modelProperty = modelProperty;
  }

  //14.5.2 定义表单模型
  getValidationMessages() {
    let messages: string[] = [];
    if (this.errors) {
      for (const errorName in this.errors) {
        switch (errorName) {
          case 'required':
            messages.push(`you must enter a ${this.lable}`);
            break;
          case 'minlength':
            messages.push(
              `a ${this.lable} must be at least ${this.errors['minlength'].requiredLength}`
            );
            break;
          case 'maxlength':
            messages.push(
              `a ${this.lable} must be no more than ${this.errors['minlength'].requiredLength}`
            );
            break;
          case 'pattern':
            messages.push(`The ${this.lable} contains illegal chracters`);
            break;
        }
      }
    }
    return messages;
  }
}

export class ProductFormGroup extends FormGroup {
  //FormGroup 管理form 元素
  constructor() {
    super({
      name: new ProductFormControl('Name', 'username', '', Validators.required),
      category: new ProductFormControl(
        'Category',
        'category',
        '',
        Validators.compose([
          Validators.required,
          Validators.pattern('^[A-Za-z ]+$'),
          Validators.minLength(3),
          Validators.maxLength(10),
        ])
      ),
      price: new ProductFormControl(
        'Price',
        'price',
        '',
        Validators.compose([
          Validators.required,
          Validators.pattern('^[0-9.]+$'),
        ])
      ),
    });
  }

  get productControls(): ProductFormControl[] {
    return Object.keys(this.controls).map(
      (k) => this.controls[k] as ProductFormControl
    );
  }

  getValidationMessages(name: string): string[] {
    return (
      this.controls[name] as ProductFormControl
    ).getValidationMessages();
  }

  getFormValidationMessages(): string[] {
    let messages: string[] = [];
    Object.values(this.controls).forEach((c) =>
      messages.push(...(c as ProductFormControl).getValidationMessages())
    );
    return messages;
  }
}

删除了用于生成错误消息的方法,它们被移动到 form.model​​

导入 ProductFormGroup 类,并且定义一个 fromGroup 属性,这样就可以使用自定义表单模型类。

//14.5.3 使用模型验证
//component.ts
import { ApplicationRef, Component } from '@angular/core';
import { Model } from './repository.model';
import { Product } from './product.model';
import { NgForm } from '@angular/forms';
import { ProductFormGroup } from './form.module';

@Component({
  selector: 'app',
  templateUrl: 'template.html',
})
export class ProductComponent {
  model: Model = new Model();
  formGroup: ProductFormGroup = new ProductFormGroup();
  selectedProduct: string = '';

  //是否已经提交
  formSubmitted: boolean = false;

  submitForm() {
    Object.keys(this.formGroup.controls)
      .forEach(c=> this.newProduct[c] = this.formGroup.controls[c].value);//如果错误:No index signature with a parameter of type 'string' was found on type 'Product'. => Product 类定义 [key: string]: any;

    this.formSubmitted = true;
    if (this.formGroup.valid) {
      this.addProject(this.newProduct);
      this.newProduct = new Product();
      this.formGroup.reset();
      this.formSubmitted = false;
    }
  }

 newProduct: Product = new Product();

  get jsonProduct() {
    return JSON.stringify(this.newProduct);
  }

  addProject(p: Product) {
    console.log('new project: ' + this.newProduct);
  }

}

下面是HTML,

<!-- 14.5.3 使用模型验证 -->
<style>
  input.ng-dirty.ng-invalid {
    border: 2px solid #ff0000
  }

  input.ng-dirty.ng-valid {
    border: 2px solid #6bc502
  }
</style>

<div class="m-2">
  <div class="bg-info text-white mb-2 p-2">Model Data:{{jsonProduct}}</div>

  <form class="m-2" novalidate [formGroup]="formGroup" (ngSubmit)="submitForm()">
    <div class="bg-danger text-white p-2 mb-2" *ngIf="formSubmitted && formGroup.invalid">
      There are problems with the form
      <ul>
        <li *ngFor="let error of formGroup.getFormValidationMessages()">
          {{error}}
        </li>
      </ul>
    </div>

    <div class="form-group">
      <label>Name</label>
      <input class="form-control" name="name" formControlName="name" />
      <ul class="text-danger list-unstlyed"
        *ngIf="(formSubmitted || formGroup.controls['name'].dirty) && formGroup.controls['name'].invalid">
        <li *ngFor="let error of formGroup.getValidationMessages('name')">
          <span>{{error}}</span>
        </li>
      </ul>
    </div>

    <div class="form-group">
      <label>Category</label>
      <input class="form-control" name="price" formControlName="category" />
      <ul class="text-danger list-unstlyed"
        *ngIf="(formSubmitted || formGroup.controls['category'].dirty) && formGroup.controls['category'].invalid">
        <li *ngFor="let error of formGroup.getValidationMessages('category')">
          <span>{{error}}</span>
        </li>
      </ul>
    </div>

    <div class="form-group">
      <label>Price</label>
      <input class="form-control" name="price" formControlName="price" />
      <ul class="text-danger list-unstlyed"
        *ngIf="(formSubmitted || formGroup.controls['price'].dirty) && formGroup.controls['price'].invalid">
        <li *ngFor="let error of formGroup.getValidationMessages('price')">
          <span>{{error}}</span>
        </li>
      </ul>
    </div>

    <button class="btn btn-primary" type="submit" 
    [disabled]="formSubmitted && formGroup.invalid"
      [class.btn-secondary]="formSubmitted && formGroup.invalid">
      Create
    </button>
  </form>
</div>

第1个修改:form

<form class="m-2" novalidate [formGroup]="formGroup" (ngSubmit)="submitForm()">

​[formGroup]="formGroup"​​ 赋给 formGroup 指令的值是 form​​ 属性,这里是 formGroup,返回一个 ProductFormGroup​​ 对象。

第2个修改:input

​删除​​了单独的验证属性和被赋予特殊值 ngForm​​ 的模板变量。但是添加了 foprmControlName​​ 属性,模型表单这里使用 ProductFormGroup 使用名称来识别。

foprmControlName 让 angular 添加和移除 input元素的验证类。告诉 angular,用特定的验证器验证。

···
name: new ProductFormControl('Name', 'username', '', Validators.required),
···

第3个修改: ul

FormGroup 提供一个 controls 属性,返回自己管理的 FormControl 对象集合,按名称进行索引。

export class Product {
  constructor(
    public id?: number,
    public name?: string,
    public category?: string,
    public price?: number
  ) {}
  [key: string]: any;
}

标签:控件,FormControl,验证,表单,new,FormGroup,angular
From: https://www.cnblogs.com/tangge/p/18200844

相关文章

  • 【uniapp 篇 】动态添加 表单,所添加元素展示在同一行
    动态添加表单,所添加元素展示在同一行1<uni-formslabelWidth="68px">23<uni-forms-itemv-for="(item,index)inbaseFormData.dynamicTable.timeField.array"4......
  • Angular-2-示例-全-
    Angular2示例(全)原文:zh.annas-archive.org/md5/529E3E7FE7FFE986F90814E2C501C746译者:飞龙协议:CCBY-NC-SA4.0前言Angular2来了,我们非常兴奋!这本书让我们能够与您联系,并在您学习Angular2的过程中伸出援手。虽然Angular1的增长是有机的,但Angular2却不能这样说......
  • Angular2-切换指南-全-
    Angular2切换指南(全)原文:zh.annas-archive.org/md5/AE0A0B893569467A0AAE20A9EA07809D译者:飞龙协议:CCBY-NC-SA4.0前言AngularJS是一个使构建Web应用程序更容易的JavaScript开发框架。它如今被用于大规模、高流量的网站,这些网站在性能不佳、可移植性问题、SEO不友好......
  • Angular2-Bootstrap4-Web-开发-全-
    Angular2Bootstrap4Web开发(全)原文:zh.annas-archive.org/md5/1998a305c23fbffe24116fac6b321687译者:飞龙协议:CCBY-NC-SA4.0前言这本书是关于当代网页开发中两个巨大和最受欢迎的名字,Angular2和Bootstrap4。Angular2是AngularJS的继任者,但在许多方面都比前任更......
  • Angular-测试驱动开发-全-
    Angular测试驱动开发(全)原文:zh.annas-archive.org/md5/60F96C36D64CD0F22F8885CC69A834D2译者:飞龙协议:CCBY-NC-SA4.0前言本书将为读者提供一个关于JavaScript测试驱动开发(TDD)的完整指南,然后深入探讨Angular的方法。它将提供清晰的、逐步的示例,不断强调TDD的最佳实......
  • java中的http请求的封装(GET、POST、form表单形式)
    前JAVA实现HTTP请求的方法用的最多的有两种:一种是通过HTTPClient这种第三方的开源框架去实现。HTTPClient对HTTP的封装性比较不错,通过它基本上能够满足我们大部分的需求,HttpClient3.1是org.apache.commons.httpclient下操作远程url的工具包,虽然已不再更新,但实现工作中使用httpC......
  • Angular | 理解数据绑定
    1.什么是数据绑定,怎么实现就是实现数据和html模板之前的联通,就叫数据绑定。数据绑定分为单向数据绑定和双向数据绑定:单向数据绑定和双向数据绑定可通过"[]","()"来实现分别实现绑定属性值和方法来实现单向数据绑定可通"([])"来实现双向数据绑定,一般应用于表单输入和其他用户输......
  • form 表单查询本身存在转义字符的处理
    碰到的问题是 查询的时候填写这样的字符串:packageCenter/xxxx?scene=t%3Dp_c67bd3exxxxxxxxx用php的自带的超全局变量 $_GET获取到的是解码后的的字符串:packageCenter/xxxx?scene=t=p_c67bd3exxxxxxxxx。解决方案:使用$_SERVER['QUERY_STRING']来获取未解码前的查询字......
  • Angular Material 17+ 高级教程 – Material Tooltip
        目录上一篇 AngularMaterial17+高级教程–CDKOverlay下一篇TODO想查看目录,请移步 Angular17+高级教程–目录......
  • VueJS-表单构建指南-全-
    VueJS表单构建指南(全)原文:zh.annas-archive.org/md5/89D4502ECBF31F487E1AF228404A6AC0译者:飞龙协议:CCBY-NC-SA4.0前言Vue.js是世界领先和增长最快的前端开发框架之一。其平缓的学习曲线和充满活力和乐于助人的社区使其成为许多新开发人员寻求利用前端框架的力量的不二......