[Angular] Day23. Introduction to forms in Angular

从本章开始会进入 Angular Form 的部分,在现代网页中与使用者互动的过程变得越来越重要,其中最主要的便是 Form,从一开始的登陆页面到对商品或对网页中元素的设定都需要使用到 Form 来取得使用者输入的资料,也需要对使用者输入的资料做验证以防使用者输入了非格式的数据导致出错。

Angular 提供了两种不同的方法来处理使用者的输入,分别是 reactivetemplate-driven,两种方法都是从 view 中捕获使用者输入事件、验证用户输入、创建表单模型或更新数据模型,本篇会大概介绍这两种方法以及他们之间的差别,那就继续往下看吧!

https://ithelp.ithome.com.tw/upload/images/20210821/20124767cYDUalx1rV.png


Choosing an approach

Reactive formstemplate-driven forms 以不同的方式处理和管理表单数据,他们各有各自的优势:

  • Reactive forms:提供对底层表单 object 的最直接且明显的访问,具有更高的可扩展性、可重用性和可测试性。
  • Template-driven forms:依赖 template 中的 directive 来创建或操作底层表单 object,常用於添加简单的表单,他很容易被添加到专案中但他的扩展性与可测试性比较差。

Key differences

可以透过一张表单来总结 Reactive formstemplate-driven forms 之间的主要区别

'' Reactive Template
Setup of form model 明显的,在 Component 中创建 隐式的,由 directive 创建
Data model 结构化和不可变 非结构化且可变
Data flow 同步 非同步
Form validation 透过 function 透过 directive

Scalability

如果 Form 是你专案中的核心部分的话,那麽就需要将整个 Form 模型重复使用於各个不同的 Component,所以可伸缩性就非常重要。

Reactive forms 比 template-driven forms 更具有可扩展性 ,他提供了对底层表单 API 的直接访问,并在 view 和数据模型之间使用同步的数据流,这让你创建大型的表单变得更加容易,他也需要比较少的测试设置,并且测试不需要深入了解就可以正确的测试表单的更新和验证。

相较於 Reactive forms 而言 Template-driven forms 比较常用在 简单且不可重复使用的场景,他对底层表单 API 的访问比较抽象,并在 view 与数据模型之间使用非同步的数据流,由於他的对 API 的访问较为抽象所以对於测试来说比较不好撰写,非常需要依赖手动更改检测执行才能成功,需要非常多的设置。


Setting up the form model

Reactive formstemplate-driven forms 都会追踪使用者与画面中的表单和 Component 中表单数据之间的变化,他们在底层来说是共享同一个地层构建模块,但在创建和管理表单控制实例的方法不同。

Common form foundation classes

Reactive forms 与 template-dirven forms 都建立在以下的 base classes 之上:

  • FormControl:会追踪单个表单控制元件的值和验证状态,简单来说他会负责追踪画面中一个绑定的 <input> 元素的内容。
  • FormGroup:会追踪多个表单控制元件的值和状态,简单来说他像是 javeascript 的 object,里面包含了很多个不同的表单控制元件,可能由多个 FormControl 或 FormArray 所组成,甚至里面在包含一个 FormGroup。
  • FormArray: 会追踪表单控制阵列中的值和状态,简单来说可以想像是 FormControl 的阵列。
  • ControlValueAccessor:在 Angular FormControl instnce 与 DOM 之间建立了一个沟通的桥梁。

Setup in reactive forms

使用 reactive forms 可以直接在 component 中定义你的表单模型,[formControl] directive 会使用 internal value accessor 将创建的 FormControl intance 链结到 view 中被绑定的单个输入元素,来举个例子吧

  1. 首先先在 app.module.ts 中引入 Form module

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    
    @NgModule({
      declarations: [
        AppComponent,
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        FormsModule,
        ReactiveFormsModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    
  2. 在 app.component.ts 中建立 FormControl

    import { Component } from '@angular/core';
    import { FormControl } from '@angular/forms';                 // (1)
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent {
      favoriteColorControl = new FormControl('');                 // (2)
    }
    
    • (1): 从 @angular/forms 中引入 FormControl
    • (2): 将 FormControl 实例化并赋予给 component 的 property
  3. 在 app.component.html 中绑定表单控制元件

    <!-- app.component.html -->
    
    <div>
      Favorite Color: <input type="text" [formControl]="favoriteColorControl" />
    </div>
    
    

https://ithelp.ithome.com.tw/upload/images/20210821/20124767foRImuOGGA.png

img

<input> 元件与 formControl 绑定後,他会追踪画面中 <input> 内容的变化并将它传递给 component 中被绑定的 property ( favoriteColorControl ),其实跟着上面的范例是无法在 console 中获得输入的值,要怎麽获得我之後会详细讲解。

Setup in template-driven forms

接着来看如何建立 template-dirven forms 的表单控制模型,在 template-dirven forms 中的控制模型是隐性的,使用 directive MgModel 为给定的表单元素创建和管理一个 formControl 实例,一样举个例子

  1. 在 app.component.ts 中新增一个 property

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-template-favorite-color',
      templateUrl: './app.component.html',
    })
    export class FavoriteColorComponent {
      favoriteColor = '';
    }
    
  2. 在 app.component.html 中使用 NgModel 绑定刚刚建立的 property

    <!-- app.component.html -->
    
    <div>
      Favorite Color: <input type="text" [(ngModel)]="favoriteColor" />
    </div>
    

https://ithelp.ithome.com.tw/upload/images/20210821/20124767k4yN0zPmBx.png

img

在画面中可以看到透过 NgModel 将指定的 property 绑定,当画面中的表单元素内容发生变化时会传递给 component 中被绑定的 property,一样上面的范例是无法看到 console 的内容之後会讲解。


Data flow in forms

介绍完如何绑定表单的控制元件後,接着要来看看 Form 的数据流,当你的专案中有使用表单时,Angular 必须保持 view 与 component 模型内容的同步,当使用者更改画面中的值时这个新的值需要立即的反映在数据模型中,相同的当数据模型的值更新时也需要立马反映在画面中,而两种不同的 form 有着不同的处理数据与更改画面的方式。

Data flow in reactive forms

在 reactive forms 中 view 的每一个表单元素都直接链结到 component 中的表单模型(formControl instance),从 view 到表单模型以及表单模型到 view 的更新是同步的,不依赖於 UI 的呈现方式。

view 到表单模型显示了当在画面中输入字串後从 view 通过以下步骤流向表单模型:

  1. 使用者在 <input> 中输入了一个值,例子中是输入了喜欢的颜色(blue)。
  2. 表单输入元素(<input>)发送了带有最新值(blue) 的输入事件
  3. control value accessor 会监听输入元素的事件,当接收到事件後会立即将最新的值中继到 FormControl 实例上
  4. FormControl 实例会通过 valueChanges 这个 observable 发出最新的值( valueChange.subscribe(val => { ... }) )。
  5. valueChanges observable 的任何订阅者都会收到这个最新的值。

https://ithelp.ithome.com.tw/upload/images/20210821/2012476752sXiPKza9.png

而表单模型到 view 显示了对表单模型进行编成更改後如何通过以下步骤更新 view 的值:

  1. 调用 setValue() method 用於更新 FormControl 的内容。
  2. FormControl 实例通过 valueChanges observable 发出新的值。
  3. valueChanges observable 的任何订阅者都会收到新的值。
  4. control value accessor 使用这个最新的值更新画面表单元素

https://ithelp.ithome.com.tw/upload/images/20210821/20124767KNa7RJO17u.png

Data flow in template-driven forms

在 template-driven forms 中每个表单元素都链结到一个内部管理表单模型的 directive,从 view 到模型显示了当在画面中输入字串後从 view 通过以下步骤流向表单模型:

  1. 使用者在画面中的 <input> 中输入新的值(blue)。
  2. <input> 元素发出一个带有 blue 的 input 事件
  3. 附加在 <input>control value accessor 会触发 FormControl 实例上的 setValue() method。
  4. FormControl 实例通过 valueChanges observable 发出新的值。
  5. valueChanges observable 的任何订阅者都会收到新的值。
  6. control value accessor 还会调用 NgModel.viewToModelUpdater(),这个 method 会发出一个 ngModelChange 的事件。
  7. 因为 component 的 template 对 favoriteColor property 使用双向属性绑定 (Two-way binding) ,所以 component 中的 favoriteColor property 被更新为 ngModelChange 事件发出的值 (blue)。

https://ithelp.ithome.com.tw/upload/images/20210821/20124767cOKhVGuSFk.png

表单模型到 view 显示了当 favoriteColor 从蓝色变为红色时,数据会如何从表单模型流向 view,通果以下步骤:

  1. 在 component 中更新 property favoriteColor 的值。
  2. 变更检测开始。
  3. 在变更检测期间,Lifecycle method ngOnChangesNgModel directive 实例上被调用,因为他的 input binding 发生更改。
  4. ngOnChanges() 透过非同步的方式设置 FormControl 实例的值
  5. 变更检测完成。
  6. FormControl 实例通过 valueChanges observable 发出最新的值。
  7. valueChanges observable 的任何订阅者都会收到新的值。
  8. control value accessor 会使用最新的 favoriteColor 值更新 view 中的表单输入元素

https://ithelp.ithome.com.tw/upload/images/20210821/20124767ZCtQxTcxVF.png

Mutability of the data model

更改和追踪的 method 在两种不同的 form 会有不同的作用:

  • Reactive forms:通过将表单数据模型作为不可更改的数据结构用来保持表单数据模型的纯净,每次在表单数据模型上触发更改时,FormControl 实例都会返回一个新的表单数据模型而不是更改现有的模型,这使你可以通过 FormControl 的 Observable 追踪表单数据模型的更改,让更改检测更有效,因为他只要追踪 FormControl 的更改,由於数据的更新遵循 reactive 的模式,所以可以使用 observable 的操作符改变表单数据。
  • Template-driven forms:依靠双向数据绑定的可变性在 template 中进行更改後也更改 component 中的 property,由於在使用双向数据绑定时没有办法只跟踪表单数据模型,所以效率较低。

以上面例子而言两种不同的 form 对於数据的变更处理:

  • 对於 reactive forms 来说,当 FormControl 的值更新时,会返回一个新的值。
  • 对於 template-driven forms 来说当更新值时会将上一个内容取代为新的值。

结论

本篇中介绍了 Angular 的两种不同的 Form,分别是 Reactive formstemplate-driven forms,虽然两种方法都可以做到与使用者的互动,但是还是有些的不同,Reactive forms 有更高的可扩展性、可重用性和可测试性,而 template-driven forms 则是使用双向绑定的方式将 component 的 property 与 template 的输入元件绑定,所以常用在比较简单的 Form 结构上。

虽然在本篇中简单的介绍了该如何使用 Reactive formstemplate-driven forms,不过只有稍微提到而已,接下来会分别对他们两个做比较详细的介绍与如何使用,那就明天见吧!


Reference


<<:  伪类与伪元素-30天学会HTML+CSS,制作精美网站

>>:  【Day08】Git 版本控制 - GitHub 简介

Day26-JDK可视化监控工具:visualVM(二)

前言 上篇介绍了visualVM的安装,这篇就要来介绍如何使用 范例我们拿Day23-JDK可视化监...

【Day05】Data Flow 与 State

Data Flow 中文直译为资料流, React 中文圈通常说法是 单向资料流/单一资料流, 如字...

JS 物件属性:属性的特徵 DAY68

Object.defineProperty 定义物件属性,调整属性特徵(请牢记!!) // 定义物件...

笔记:好用HTML5 的表单input元件及属性

html的form标签之input小记录 前言本篇是上课中所提及好用,但是笔者小新手之前未发现的上课...

Git

最初,Linux Kernel 的社群采用压缩档或是补丁的方式进行维护工作。一直到 2002 年,开...