Angular表单处理(模板驱动)

模板驱动表单是Angular中两种表单处理方式之一(另一种是响应式表单)。它通过在模板中使用指令来构建表单,适合简单的表单场景。

重要提示:使用模板驱动表单前,需要在AppModule中导入FormsModule

1. 基本表单创建

创建一个简单的用户注册表单:

<!-- app.component.html -->
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
    <div class="form-group">
        <label for="name">用户名</label>
        <input
            type="text"
            id="name"
            name="name"
            [(ngModel)]="user.name"
            #name="ngModel"
            required
            minlength="3"
            class="form-control">

        <div *ngIf="name.invalid && (name.dirty || name.touched)" class="error-msg">
            <div *ngIf="name.errors?.['required']">用户名是必填项</div>
            <div *ngIf="name.errors?.['minlength']">用户名至少3个字符</div>
        </div>
    </div>

    <div class="form-group">
        <label for="email">邮箱</label>
        <input
            type="email"
            id="email"
            name="email"
            [(ngModel)]="user.email"
            #email="ngModel"
            required
            email
            class="form-control">

        <div *ngIf="email.invalid && (email.dirty || email.touched)" class="error-msg">
            <div *ngIf="email.errors?.['required']">邮箱是必填项</div>
            <div *ngIf="email.errors?.['email']">请输入有效的邮箱地址</div>
        </div>
    </div>

    <div class="form-group">
        <label for="password">密码</label>
        <input
            type="password"
            id="password"
            name="password"
            [(ngModel)]="user.password"
            #password="ngModel"
            required
            pattern="^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$"
            class="form-control">

        <div *ngIf="password.invalid && (password.dirty || password.touched)" class="error-msg">
            <div *ngIf="password.errors?.['required']">密码是必填项</div>
            <div *ngIf="password.errors?.['pattern']">密码必须至少8个字符,包含字母和数字</div>
        </div>
    </div>

    <button type="submit" [disabled]="userForm.invalid" class="btn btn-primary">
        注册
    </button>
</form>
// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  user = {
    name: '',
    email: '',
    password: ''
  };

  onSubmit(form: any) {
    if (form.valid) {
      console.log('表单数据:', this.user);
      console.log('表单值:', form.value);
      // 这里可以发送数据到服务器
      alert('注册成功!');
      form.reset();
    }
  }
}

2. 表单验证

Angular提供的内置验证器:

<!-- 内置验证器示例 -->
<input type="text" [(ngModel)]="model.value" name="field" required>
<input type="email" [(ngModel)]="model.email" name="email" email>
<input type="number" [(ngModel)]="model.age" name="age" min="18" max="100">
<input type="text" [(ngModel)]="model.username" name="username" minlength="3" maxlength="20">
<input type="text" [(ngModel)]="model.zipcode" name="zipcode" pattern="[0-9]{5}">

3. 单选按钮和复选框

<!-- 单选按钮组 -->
<div class="form-group">
    <label>性别</label>
    <div class="form-check">
        <input
            type="radio"
            id="male"
            name="gender"
            [(ngModel)]="user.gender"
            value="male"
            class="form-check-input">
        <label for="male" class="form-check-label">男</label>
    </div>
    <div class="form-check">
        <input
            type="radio"
            id="female"
            name="gender"
            [(ngModel)]="user.gender"
            value="female"
            class="form-check-input">
        <label for="female" class="form-check-label">女</label>
    </div>
</div>

<!-- 复选框 -->
<div class="form-group">
    <div class="form-check">
        <input
            type="checkbox"
            id="subscribe"
            name="subscribe"
            [(ngModel)]="user.subscribe"
            class="form-check-input">
        <label for="subscribe" class="form-check-label">订阅新闻邮件</label>
    </div>
</div>

<!-- 多选框组 -->
<div class="form-group">
    <label>兴趣爱好</label>
    <div class="form-check" *ngFor="let hobby of hobbies">
        <input
            type="checkbox"
            [id]="hobby.id"
            [value]="hobby.value"
            [checked]="user.hobbies?.includes(hobby.value)"
            (change)="onHobbyChange($event, hobby.value)"
            class="form-check-input">
        <label [for]="hobby.id" class="form-check-label">{{hobby.label}}</label>
    </div>
</div>
// 组件中的处理逻辑
hobbies = [
  { id: 'sports', value: 'sports', label: '运动' },
  { id: 'reading', value: 'reading', label: '阅读' },
  { id: 'music', value: 'music', label: '音乐' }
];

user = {
  gender: 'male',
  subscribe: true,
  hobbies: ['sports', 'music']
};

onHobbyChange(event: any, hobbyValue: string) {
  if (event.target.checked) {
    if (!this.user.hobbies.includes(hobbyValue)) {
      this.user.hobbies.push(hobbyValue);
    }
  } else {
    const index = this.user.hobbies.indexOf(hobbyValue);
    if (index > -1) {
      this.user.hobbies.splice(index, 1);
    }
  }
}

4. 下拉选择框

<!-- 基本下拉框 -->
<div class="form-group">
    <label for="country">国家</label>
    <select
        id="country"
        name="country"
        [(ngModel)]="user.country"
        class="form-control">
        <option value="">请选择国家</option>
        <option *ngFor="let country of countries" [value]="country.code">
            {{country.name}}
        </option>
    </select>
</div>

<!-- 多选下拉框 -->
<div class="form-group">
    <label for="languages">掌握的语言</label>
    <select
        id="languages"
        name="languages"
        [(ngModel)]="user.languages"
        multiple
        class="form-control">
        <option *ngFor="let lang of languages" [value]="lang">
            {{lang}}
        </option>
    </select>
</div>

5. 表单状态和CSS类

Angular会自动为表单控件添加CSS类,反映其状态:

状态 为真时 为假时
ng-valid 控件通过验证 控件未通过验证
ng-invalid 控件未通过验证 控件通过验证
ng-pristine 控件值未改变 控件值已改变
ng-dirty 控件值已改变 控件值未改变
ng-touched 控件已被访问过 控件未被访问
ng-untouched 控件未被访问 控件已被访问过
/* 自定义验证样式 */
input.ng-valid {
    border-color: #28a745;
}

input.ng-invalid.ng-touched {
    border-color: #dc3545;
}

input.ng-pending {
    border-color: #ffc107;
}

.form-submitted input.ng-invalid {
    border-color: #dc3545;
}

6. 完整示例:用户注册表单

用户名可用
请输入有效的邮箱地址
最佳实践:
  • 为每个表单控件提供清晰的标签
  • 使用恰当的输入类型(email、number等)
  • 在用户交互后显示验证信息
  • 禁用提交按钮直到表单有效
  • 考虑可访问性,使用ARIA属性

7. 模板驱动表单 vs 响应式表单

特性 模板驱动表单 响应式表单
设置方式 在模板中通过指令 在组件类中创建
数据模型 非显式,自动创建 显式,不可变对象
可预测性 异步 同步
表单验证 指令 函数
动态性 有限
适用场景 简单表单、快速原型 复杂表单、动态表单
常见问题
  • 忘记导入FormsModule:会导致ngModel等指令不可用
  • 缺少name属性:模板驱动表单需要name属性来注册控件
  • 验证时机不当:避免在用户输入前显示错误信息
  • 内存泄漏:大型表单注意性能影响