Angular 深入浅出三十天:表单与测试 Day02 - Template Driven Forms 实作 - 以登入为例

Day2

今天要来用 Template Driven Forms 的方式实作一个简单的登入系统,撇开 UI 不谈,具体的功能需求规格如下:

  • 帐号
    • 格式为 Email Address,相关规则请参考维基百科,此处则直接使用正规表示法 /^\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b$/gi 来检验,验证有误时需在栏位後方显示错误讯息:格式有误,请重新输入
    • 此栏位必填,验证有误时需需在栏位後方显示错误讯息:此栏位必填
  • 密码
    • 长度最短不得低於 8 码,验证有误时需需在栏位後方显示错误讯息:密码长度最短不得低於8码
    • 长度最长不得超过 16码
    • 此栏位必填,验证有误时需需在栏位後方显示错误讯息:此栏位必填
  • 以上验证皆需在使用者输入时动态检查
  • 任一验证有误时,登入按钮皆呈现不可被点选之状态。

规格需求看清楚之後,我们就来开始实作吧!

实作时大家可以自己开一个专案来练习,抑或是用 Stackblitz 开一个 Angular 的专案来练习,我就不再赘述罗!

如果正在阅读此篇文章的你还不知道要怎麽开始一个 Angular 专案的话,请先阅读我的 Angular 深入浅出三十天後再来阅读此系列文章会比较恰当噢!

实作开始

首先我们先准备好基本的 HTML :

<form>
  <p>
    <label for="account">帐号:</label>
    <input type="email" name="account" id="account">
  </p>
  <p>
    <label for="password">密码:</label>
    <input type="password" name="password" id="password">
  </p>
  <p>
    <button type="submit">登入</button>
  </p>
</form>

未经美化的画面应该会长这样:

Template view

接着到 app.module.ts 里 import FormsModule

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

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

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

然後将要绑在 Template 的属性跟方法都准备好:

export class AppComponent {
  
  // 绑定在帐号栏位上
  account = '';

  // 绑定在密码栏位上
  password = '';
  
  // 帐号栏位的错误讯息
  accountErrorMessage = '';
  
  // 密码栏位的错误讯息
  passwordErrorMessage = '';

  /**
   * 绑定在帐号栏位上,当使用者改变登入帐号时,会触发此函式,并取得对应的错误讯息
   *
   * @param {string} account
   * @param {ValidationErrors} errors
   */
  accountValueChange(account: string, errors: ValidationErrors): void {
    this.account = account;
    this.validationCheck(errors, 'account');
  }


  /**
   * 绑定在密码栏位上,当使用者改变密码时会触发此函式
   * 
   * @param {string} password
   * @param {ValidationErrors} errors
   */
  passwordValueChange(password: string, errors: ValidationErrors): void {
    this.password = password;
    this.validationCheck(errors, 'password');
  }

  // 绑定在表单上,当使用者按下登入按钮时会触发此函式
  login(): void {
    // do login...
  }

  /**
   * 透过栏位里的 ValidationErrors 来设定该栏位的错误讯息
   * 
   * @param {ValidationErrors} errors 欲验证的栏位的错误 (by Angular)
   * @param {'account' | 'password'} fieldName 栏位名称
   */
  private validationCheck(
    errors: ValidationErrors,
    fieldName: 'account' | 'password'
  ): void {
    let errorMessage: string;
    if (!errors) {
      errorMessage = '';
    } else if (errors.required) {
      errorMessage = '此栏位必填';
    } else if (errors.pattern) {
      errorMessage = '格式有误,请重新输入';
    } else if (errors.minlength) {
      errorMessage = '密码长度最短不得低於8码';
    }
    this.setErrorMessage(fieldName, errorMessage);
  }

  /**
   * 设定指定栏位的错误讯息
   * 
   * @param {'account' | 'password'} fieldName 欲设定错误讯息的栏位名称
   * @param {string} errorMessage 欲设定的错误讯息
   */
  private setErrorMessage(
    fieldName: 'account' | 'password',
    errorMessage: string
  ): void {
    if (fieldName === 'account') {
      this.accountErrorMessage = errorMessage;
    } else {
      this.passwordErrorMessage = errorMessage;
    }
  }

}

就可以将这些属性和方法跟 Template 绑定在一起:

<form #form="ngForm" (ngSubmit)="login()">
  <p>
    <label for="account">帐号:</label>
    <input
      type="email"
      name="account"
      id="account"
      required
      pattern="\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b"
      #accountNgModel="ngModel"
      [ngModel]="account"
      (ngModelChange)="accountValueChange(accountNgModel.value, accountNgModel.errors)"
    />
    <span class="error-message">{{ accountErrorMessage }}</span>
  </p>
  <p>
    <label for="password">密码:</label>
    <input
      type="password"
      name="password"
      id="password"
      required
      #passwordNgModel="ngModel"
      [minlength]="8"
      [maxlength]="16"
      [ngModel]="password"
      (ngModelChange)="passwordValueChange(passwordNgModel.value, passwordNgModel.errors)"
    />
    <span class="error-message">{{ passwordErrorMessage }}</span>
  </p>
  <p>
    <button type="submit" [disabled]="form.invalid">登入</button>
  </p>
</form>

到目前为止的程序码你看懂了多少呢?容我稍微说明一下:

  1. 首先是关於必填检核,只要 <input ...> 栏位里加上 HTML 原生的属性 ─ required 即可。

  2. 帐号栏位的格式检查则是使用原生的属性 ─ pattern ,这个属性可以直接使用正规表示法的方式来检查使用者所输入的值是否符合我们所订定的格式。不过要注意的是,头尾不需要特别加上 /^$/ ,所以整串表示法只需要用到中间的部份 ─ \b[\w\.-]+@[\w\.-]+\.\w{2,4}\b

对这个属性较不熟悉的朋友可以参照 MDN 的说明文件

  1. 字数长度的检核也是使用原生的属性 ─ minlengthmaxlength 。这部份有两个地方需要特别留意:

    1. 字数长度的检核不会管你的字元是半型还是全型、是英文还是中文,每个字元都是一样以一个长度来计算,如果有特别需求就不能使用这个方式处理。
    2. HTML 的原生属性 ─ maxlength 是会阻挡使用者输入的,当需求是要检核长度但不能阻挡使用者输入的话,就不能使用这个方式。
  2. 很多人刚学会用 Angular 的朋友,在使用 ngModel 时都会忘记这两件事情:

    1. 引入 FormsModule
    2. input 要有 name 属性
  3. 使用范本语法 #accountNgModel="ngModel"#passwordNgModel="ngModel" 来取得型别为 NgModel 的物件,因为我们可以从中取得该栏位的 valueerrors ,前者指定给其相关属性,後者用以判断该栏位的错误,以设定相对应的错误讯息。

单纯使用 #accountNgModel#accountNgModel="ngModel" 的差别在於前者取得的是单纯的 HTMLInputElement 物件。

  1. 使用范本语法 #form="ngForm" 来取得型别为 NgForm 的表单物件。

单纯使用 #form#form="ngForm" 的差别在於前者取得的是单纯的 HTMLFormElement 物件。

  1. 最後,则是将登入按钮加上 [disabled]="form.invalid" 的绑定,让按钮在表单无效时,无法按下登入按钮。

至此,我们就完成今天的目标罗!是不是很简单呢?!

最後的结果应该要像这样:

complete gif

本日小结

刚开始学习 Angular 的朋友,通常都会因为不熟悉 Angular 的语法而导致明明很简单的功能却要弄得很复杂。

今天的学习重点主要有以下三点:

  1. 学习如何使用 Angular 的范本语法取得 Angular 已经包装好的物件,例如 #accountNgModel="ngModel"#form="ngForm"
  2. 学习使用表单物件 NgModelNgForm
  3. 善用 NgModel 里的 ValidationErrors 取得相应的错误讯息。

我会将今日的实作程序码放在 Stackblitz 上供大家参考,建议大家在看我的实作之前,先按照需求规格自己做一遍,之後再跟我的对照,看看自己的实作跟我的实作不同的地方在哪里、有什麽好处与坏处,如此反覆咀嚼消化後,我相信你一定可以进步地非常快!

如果有任何的问题或是回馈,也都非常欢迎留言给我让我知道噢!

错误回报更新

  • 2021-09-19 22:54:50 ,感谢热心读者「程凯大」指正错误,已移除所有的 FormControl ,原因是因为在 Template Driven Forms 的范围里, NgModel 本身已有我们所需之属性,是我自己猪头舍近求远,再次衷心感谢。

<<:  Day02-Vue.js的实体

>>:  【Day17】 data-test这个属性是用来干嘛的...? 这东西能吃吗 (╯✧∇✧)╯ !?

AE卷轴制作1-Day2

练习范例教学 六指渊:https://www.sixvfx.com/rolling_paper 开始...

Spring boot 与 MongoDB 之连线

MongoDB 是一个 NoSQL 实现。NoSQL 在具有高吞吐量的应用程序中可以非常高性能。 没...

Day 27 - 建立自己的K线资料库 (中)

本篇重点 透过Pandas读取资料及做OHLC转换 DataFrame.resample中的Left...

【在 iOS 开发路上的大小事-Day15】透过 Firebase 来管理使用者 (Sign in with E-mail 篇) Part1

这篇会来教大家如何透过 Firebase 在你的 iOS App 上实作注册以及登入功能 透过 Fi...

有关版本控制

在开发的过程一定会面临各式的测试,以及功能的增减。对於文件的编写也会有相同的要求。一般来说,个人开发...