解锁角反应形式的力量
#javascript #网络开发人员 #angular #rxjs

Angular的反应形式模块是创建和管理复杂形式逻辑的强大工具。它使应用程序能够以清晰,可预测和可扩展的方式响应用户输入。反应性形式的核心特征之一是其基于可观察的API,它使我们能够构建反应性UI,以随着时间的流逝而反应变化。

角反应性形式由Form ControlForm GroupForm Array等构件组成。这些构建块允许您创建和组织形式控件,组相关输入,并动态处理多个输入。

Image description

反应性形式的核心概念

Angular的反应性形式是围绕可观察到的流进行设计的,为开发人员提供了完全控制的,以在任何给定时刻管理形式的状态。这些形式的结构以模型驱动的方法为基础,受到基础数据模型的影响,而不是用户界面(UI)。

本文将指导您介绍角反应式形式的关键特征,并提供为每个方面提供角组件代码。

探索构建块

1。表格组:基础

FormGroupFormControl对象的集合。它将每个子FormControl的值汇总到一个对象中,每个控件名称用作密钥。

下面是使用FormGroup的组件的一个示例:

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

@Component({
  selector: 'app-profile-editor',
  template: `
    <form [formGroup]="profileForm">
      <label>
        First Name:
        <input type="text" formControlName="firstName">
      </label>
      <label>
        Last Name:
        <input type="text" formControlName="lastName">
      </label>
    </form>
  `,
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
}

2。表单数组:管理动态输入

FormArray提供了FormGroup的替代方法,用于管理任何数量的未命名控件。与FormGroup固定的命名控件不同,FormArrays通常管理动态添加的控件。

这是一个带有FormArray的组件:

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

@Component({
  selector: 'app-profile-editor',
  template: `
    <form [formGroup]="profileForm">
      <div formArrayName="aliases">
        <div *ngFor="let alias of aliases.controls; let i=index">
          <input [formControlName]="i">
        </div>
      </div>
      <button (click)="addAlias()">Add Alias</button>
    </form>
  `,
})
export class ProfileEditorComponent {
  profileForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.profileForm = this.fb.group({
      aliases: this.fb.array([
        this.fb.control('')
      ])
    });
  }

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

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

3。表单验证:确保输入质量

Angular提供了一组内置验证器,您还可以编写自己的自定义验证器。验证器是处理FormControl或控件集合并返回错误或null的功能。

这是您将验证添加到FormControl的方式:

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

@Component({
  selector: 'app-example',
  template: `
    <input [formControl]="name" placeholder="Enter name">
    <p *ngIf="name.invalid && (name.dirty || name.touched)" class="error">
      Name is required.
    </p>
  `,
})
export class ExampleComponent {
  name = new FormControl('', Validators.required);
}

4。利用反应性形式的可观察物

每个FormControlFormGroupFormArray实例都有一个可观察到的valueChanges。您可以订阅可观察到的时,只要对控件的值变化就可以做出反应。

this.profileForm.get('firstName').valueChanges.subscribe(value => {
  console.log('First name has changed to:', value);
});

可观察的形成了角反应性形式的骨干。它们提供了处理各种场景的机制,包括处理用户事件,管理异步操作和维持状态。

使用可观察形式的可观察物的主要优点之一是它们提供了诸如mapfilterdebounceTime等广泛的操作员的能力,从而简化了处理复杂的方案。

让我们实现一个反应性转换以监视对firstName字段的更改:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { User } from './user.model';
import { UserService } from './user.service';
import { Subject } from 'rxjs';
import { debounceTime, filter, switchMap, takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-user-form',
  template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
  <input formControlName="firstName" placeholder="First Name">
  <input formControlName="lastName" placeholder="Last Name">
  <input formControlName="email" placeholder="Email">
  <input formControlName="age" placeholder="Age">
  <button type="submit">Submit</button>
</form>`
})

export class UserFormComponent implements OnInit, OnDestroy {
  userForm = new FormGroup<User>({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    email: new FormControl(''),
    age: new FormControl(null),
  });

  userSearchResults = [];
  private destroy$ = new Subject<void>();

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userForm.get('firstName').valueChanges.pipe(
      debounceTime(400),
      filter(value => value.length > 2),
      switchMap(value => this.userService.search(value)),
      takeUntil(this.destroy$)
    ).subscribe(results => {
      this.userSearchResults = results;
    });
  }

  onSubmit(): void {
    if (this.userForm.valid) {
      this.userService.addUser(this.userForm.value);
    }
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

在此组件中,我们在ngOnInit生命周期方法中订阅了形式的firstName字段的更改。我们使用debounceTimefilterswitchMap操作员来控制我们基于用户输入的频率。

我们还使用takeUntil操作员在销毁组件时可观察到的valueChanges取消订阅,以防止潜在的内存泄漏。

提交表单时,我们使用onSubmit方法检查表单有效性,如果有效,我们将其调用带有表单值的userService.addUser

最佳实践

使用角反应性形式时,遵守某些最佳实践可能会导致更有效和可维护的代码:

  • 不变性:将您的形式的价值视为不变。而不是修改现有值,而是创建一个新值。这种方法允许Angular有效跟踪更改并更新UI。
this.userForm.setValue({...this.userForm.value, firstName: 'New name'});
  • 反应性转换:利用RXJS运算符,用于反应性变换值。这种做法可以使您的组件代码保持清洁,增强可读性,并以简单性促进复杂的转换。这是一个使用map操作员来转换输入流的示例:
this.userForm.get('age').valueChanges.pipe(
  map(value => Math.max(0, value))
).subscribe(value => {
  console.log(value);
});
  • 基于组件的设计:将您的表格作为组件组成结构,每个组件都封装了您的表单的一部分。该组织方法有助于更可维护和有组织的代码库。

将您的表格分解为较小的,易于管理的组件。例如,为用户的个人信息创建一个不同的组件:

@Component({
  selector: 'app-personal-info',
  inputs: ['parentForm: formGroup'],
  templateUrl: './personal-info.component.html'
})
export class PersonalInfoComponent {
  @Input()
  parentForm: FormGroup;
}

然后,以这样的父母形式使用它:

<form [formGroup]="userForm">
  <app-personal-info [parentForm]="userForm"></app-personal-info>
  <!-- Other form components -->
</form>
  • 错误处理:使用statusChanges可观察到的表单或单个表单控件的验证状态。该技术允许在UI中反应地显示错误消息。
this.userForm.get('firstName').statusChanges.pipe(
  filter(status => status === 'INVALID')
).subscribe(() => {
  console.log('First name is invalid');
});
  • 避免内存泄漏:时,请记住要在销毁组件时删除可观察的物品以防止内存泄漏。 takeUntil操作员提供了一种简单的方法。

当组件被破坏时,请不要忘记从valueChanges订阅:

private destroy$ = new Subject<void>();

ngOnInit(): void {
  this.userForm.get('firstName').valueChanges.pipe(
    takeUntil(this.destroy$)
  ).subscribe(value => {
    console.log(value);
  });
}

ngOnDestroy(): void {
  this.destroy$.next();
  this.destroy$.complete();
}
  • 异步验证:通过在验证器函数中返回PromiseObservable来进行异步验证。这允许对服务器端资源的表单输入验证。

异步验证的一个示例,我们检查是否采取了username

this.userForm.get('username').setAsyncValidators(this.usernameValidator.validate.bind(this.usernameValidator));

UsernameValidator服务可能看起来像这样:

@Injectable({ providedIn: 'root' })
export class UsernameValidator {
  constructor(private userService: UserService) {}

  validate(control: AbstractControl): Promise<ValidationErrors|null> | Observable<ValidationErrors|null> {
    return this.userService.isUsernameTaken(control.value).pipe(
      map(isTaken => isTaken ? { usernameTaken: true } : null),
      catchError(() => null)
    );
  }
}

在此验证器服务中,进行了后端查询以检查是否获取用户名,如果是,则返回验证错误。

Angular模板中的反应性形式控制

Angular的反应性形式提供了一组指令,以在模板中链接形式控件。这是您可以链接它们的方法:

  • formControlname :指令将独立的FormControl实例链接到表单控制元素。
  • formGroupName :此指令将FormGroup实例链接到DOM元素。
  • formarrayname :指令将FormArray实例链接到dom元素。

FormArray在Angular中是一个类,当您要创建一个动态形式时,可以允许用户添加或删除输入或输入组。当不知道输入的确切数量或一组输入时,此功能变得特别有用。

考虑一个表单方案,用户可以输入个人详细信息和多个地址(家庭,办公室等)。在这种情况下,地址可以表示为formgroup实例的formarray,每个formgroup代表一个地址。

这是如何在user-form.component.ts中实现的证明:

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

@Component({
  selector: 'app-user-form',
  templateUrl: './user-form.component.html',
})
export class UserFormComponent implements OnInit {
  userForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.userForm = this.fb.group({
      personalDetails: this.fb.group({
        firstName: ['', Validators.required],
        lastName: ['', Validators.required],
        email: ['', [Validators.required, Validators.email]],
        age: [''],
      }),
      addresses: this.fb.array([
        this.createAddressFormGroup()
      ])
    });
  }

  createAddressFormGroup(): FormGroup {
    return this.fb.group({
      street: ['', Validators.required],
      city: ['', Validators.required],
      state: ['', Validators.required],
      zip: ['', Validators.required],
    });
  }

  get addresses(): FormArray {
    return this.userForm.get('addresses') as FormArray;
  }

  addAddress() {
    this.addresses.push(this.createAddressFormGroup());
  }

  removeAddress(index: number) {
    this.addresses.removeAt(index);
  }

  onSubmit() {
    if (this.userForm.valid) {
      console.log(this.userForm.value);
    } else {
      alert("Form is invalid, please review your information");
    }
  }
}

在上面的示例中,addresses是一个FormArray,从一个地址的一个FormGroup实例开始。 createAddressFormGroup方法构造了一个地址的FormGroup。地址getter方法返回地址FormArrayaddAddressremoveAddress方法用于添加和删除地址中的地址的FormGroup实例。

现在,您可以修改表单模板(user-form.component.html)以显示动态地址表格:

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
  <div formArrayName="addresses">
    <div *ngFor="let address of addresses.controls; let i = index" [formGroupName]="i" class="form-group">
      <h3>Address {{i + 1}}</h3>

      <label>Street:</label>
      <input formControlName="street" class="form-control" placeholder="Street">
      <div *ngIf="address.get('street').errors?.required" class="alert alert-danger">
        Street is required.
      </div>

      <label>City:</label>
      <input formControlName="city" class="form-control" placeholder="City">
      <div *ngIf="address.get('city').errors?.required" class="alert alert-danger">
        City is required.
      </div>

      <label>State:</label>
      <input formControlName="state" class="form-control" placeholder="State">
      <div *ngIf="address.get('state').errors?.required" class="alert alert-danger">
        State is required.
      </div>

      <label>Zip:</label>
      <input formControlName="zip" class="form-control" placeholder="Zip">
      <div *ngIf="address.get('zip').errors?.required" class="alert alert-danger">
        Zip is required.
      </div>

      <button (click)="removeAddress(i)">Remove Address</button>
    </div>

    <button (click)="addAddress()">Add Address</button>
  </div>

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

在上面的HTML片段中,*ngFor指令用于循环循环遍布地址FormArray中的FormGroup实例。 formGroupName指令用于将每个FormGroup实例绑定到div元素。单击“删除地址”按钮时,它会触发removeadDress方法,并在单击“添加地址”按钮时调用adddress方法。

请注意,对于地址FormGroup中的每个字段,错误消息使用address.get('<field>').errors?.required检查所需的验证错误。这里的地址变量是指地址中的每个FormGroup实例。

结论

利用角反应形式的功能可以创建动态,直观和用户友好的Web应用程序。 Angular的反应性形式具有其灵活性和效率,是在角度应用中构造和处理形式的必不可少的工具。利用FormArray和Nested FormGroup之类的功能,您可以轻松地管理和验证复杂的动态形式。
享受编码!