Angular表单

案例来源于Angular官网(略作拓展)

表单

1.1 响应式表单 reactive forms

通过指令[formControl]实现,直接访问FormControl实例

通过指令内部的值访问器ControlValueAccessor,将FormControl实例和视图中的表单元素联系起来。

1.2 模板驱动表单 template-driven forms

通过NgModel指令为表单元素创建并管理FormControl实例,间接访问FormControl实例

1.3 比较

响应式模板驱动
建立表单模型显式创建,在组件类里创建隐式创建
数据模型结构化、不可变非结构化、可变
数据流同步异步
表单验证函数指令
适用场景可伸缩性强、较少的测试设置(不需要深入理解变更检测)可复用性弱、测试以来手动触发变更检测

1.4 使用

declare abstract class AbstractControl<TValue = any, TRawValue extends TValue = TValue> {
  readonly value: TValue;
  constructor(validators: ValidatorFn | ValidatorFn[] | null, asyncValidators: AsyncValidatorFn | AsyncValidatorFn[] | null);
  get valid(): boolean;
  readonly errors: ValidationErrors | null;
  readonly valueChanges: Observable<TValue>;
  addValidators(validators: ValidatorFn | ValidatorFn[]): void;
  removeValidators(validators: ValidatorFn | ValidatorFn[]): void;
  abstract setValue(value: TRawValue, options?: Object): void;
  abstract patchValue(value: TValue, options?: Object): void;
  abstract reset(value?: TValue, options?: Object): void;
  setErrors(errors: ValidationErrors | null, opts?: {
        emitEvent?: boolean;
    }): void;
  getError(errorCode: string, path?: Array<string | number> | string): any;
  hasError(errorCode: string, path?: Array<string | number> | string): boolean;
}

export declare interface FormControl<TValue = any> extends AbstractControl<TValue> {
  setValue(value: TValue, options?: {
      onlySelf?: boolean;
      emitEvent?: boolean;
      emitModelToViewChange?: boolean;
      emitViewToModelChange?: boolean;
  }): void;
  
  patchValue(value: TValue, options?: {
      onlySelf?: boolean;
      emitEvent?: boolean;
      emitModelToViewChange?: boolean;
      emitViewToModelChange?: boolean;
  }): void;
}

export export declare class FormGroup<TControl extends {
    [K in keyof TControl]: AbstractControl<any>;
} = any> extends AbstractControl<ɵTypedOrUntyped<TControl, ɵFormGroupValue<TControl>, any>, ɵTypedOrUntyped<TControl, ɵFormGroupRawValue<TControl>, any>> {
  
  controls: ɵTypedOrUntyped<TControl, TControl, {
        [key: string]: AbstractControl<any>;
    }>;
  
  addControl(this: FormGroup<{
        [key: string]: AbstractControl<any>;
    }>, name: string, control: AbstractControl, options?: {
        emitEvent?: boolean;
    }): void;
  
  removeControl(this: FormGroup<{
        [key: string]: AbstractControl<any>;
    }>, name: string, options?: {
        emitEvent?: boolean;
    }): void;
}

1.4.1 FormControl

创建表单/模板中注册表单
export class NameEditorComponent {
  name = new FormControl('', [Validators.required, this.lessThan8Words]);
}
<label for="name">Name: </label>
<input id="name" type="text" [formControl]="name">
获取/更新表单的值
<p>Value: {{ name.value }}</p>

<button type="button" (click)="updateName()">Update Name</button>
<button type="submit" (click)="getName()">Update Name</button>
updateName() {
  this.name.setValue('Nancy');
}

getName() {
  console.log(this.name.value);
}
为表单添加事件
ngOnInit(): void {
  this.name.valueChanges.subscribe((value) => {
    console.log(value); // 获取到最新的表单值
  });
}
校验表单

我们在创建表单时,为表单添加自定义校验规则(名字长度不能超过8个字母)

lessThan8Words: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
  if (control.value.length >= 8) {
    return { nameError: 'name length must less than 8.' };
  }
  return null;
};

name = new FormControl('', [Validators.required, this.lessThan8Words]);
<p *ngIf="name.hasError('nameError')" style="color: red;">{{ name.getError('nameError') }}</p>

name这个表单对象中,包含了以下几个和校验相关的常用属性

valid: boolean:表示是否符合校规则

errors:ValidationErrors:汇总了所有校验错误的属性和值

  • hasError('nameError')error对象是否包含该错误
  • getError('nameError'):获取该错误的错误信息
{
    "nameError": "name length must less than 8."
  	"required": true // 说明此时,表单里为空
}
代码
import { NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import {
  FormControl,
  ReactiveFormsModule,
  ValidationErrors,
  ValidatorFn,
  Validators,
  AbstractControl,
} from '@angular/forms';

@Component({
  standalone: true,
  selector: 'app-name-editor',
  templateUrl: './name-editor.component.html',
  styleUrls: ['./name-editor.component.css'],
  imports: [ReactiveFormsModule, NgIf],
})
export class NameEditorComponent implements OnInit {
  ngOnInit(): void {
    this.name.valueChanges.subscribe((value) => {
      console.log(value);
    });
  }

  lessThan8Words: ValidatorFn = (
    control: AbstractControl
  ): ValidationErrors | null => {
    if (control.value.length >= 8) {
      return { nameError: 'name length must less than 8.' };
    }
    return null;
  };

  name = new FormControl('', [Validators.required, this.lessThan8Words]);

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

  getName() {
    console.log(this.name.value);
  }
}
<label for="name">Name: </label>
<input id="name" type="text" [formControl]="name">

<p>Value: {{ name.value }}</p>
<p *ngIf="name.hasError('nameError')" style="color: red;">{{ name.getError('nameError') }}</p>


<button type="button" (click)="updateName()">Update Name</button>
<button type="submit" (click)="getName()">Update Name</button>

表单中通常包含多个相关的控件,响应式表单提供了FormGroupFormArray两种组合相关控件的方式

1.4.2 FormGroup

FormGroup可以理解为FormControl的组合,在使用上略有延伸。此处我们来看几个不同的地方

创建表单/模板中注册表单
export class FullNameEditorComponent implements OnInit {
  fullNameForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
}
<form [formGroup]="fullNameForm" (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">
</form>
获取/更新表单的值
public onSubmit(): void {
  console.log(this.fullNameForm?.value);
}
  • setValue:严格遵循表单的组织结构,替换控件的整个值。若传入对象的结构和定义的表单结构不同,则更新失败。
  • patchValue:替换表单中的任何属性。若传入对象的结构和定义的表单结构不同,不报错,仅更新表单模型中定义的属性。
public updateFullName(): void {
  this.fullNameForm.setValue({ firstName: 'amy', lastName: 'niu' });
  this.fullNameForm.patchValue({ firstName: 'amy' }); // 这种也能通过
}
新增/移除表单
this.fullNameForm.addControl('newControl', new FormControl(''));
this.fullNameForm.removeControl('firstName');

获取到某个FormControl对象

  • controls

    {
      firstName: FormControl,
      lastName: FormControl
    }
    
  • get('firstName')获取到firstName这个FormControl对象

1.4.3 FormArray

FormArray可以理解为FormControl的数组,在使用上略有延伸。

  • 构造函数的第一个参数即是FormControl的数组
  • 使用方式类似Array,at/push/insert等

1.4.3 FormBuilder

当我们要设计一个控件众多的表单时,重复的new FormControl非常麻烦,所以FormBuilder提供了更便利的方式生成控件。

this.formBuilder.group的返回值是FormGroup类型,故它的具体使用方式和`FromGroup基本一致,此处不重复阐述。

仅展示如何创建表单以及在模版中注册表单

import { JsonPipe, NgFor } from '@angular/common';
import { Component } from '@angular/core';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Validators } from '@angular/forms';
import { FormArray } from '@angular/forms';

@Component({
  standalone: true,
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css'],
  imports: [NgFor, ReactiveFormsModule, JsonPipe],
})
export class ProfileEditorComponent {
  profileForm = this.formBuilder.group({
    firstName: ['', Validators.required],
    lastName: [''],
    address: this.formBuilder.group({
      street: [''],
      city: [''],
      state: [''],
      zip: [''],
    }),
    aliases: this.formBuilder.array([this.formBuilder.control('')]),
  });

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

  constructor(private formBuilder: FormBuilder) {}

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

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

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <label for="first-name">First Name: </label>
  <input id="first-name" type="text" formControlName="firstName" required>

  <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>

  <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>


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

<hr>

<p>Form Value: {{ profileForm.value | json }}</p>

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值