模板驱动表单 #
一、表单概述 #
Angular提供两种表单处理方式:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 模板驱动表单 | 基于模板,简单直观 | 简单表单、快速开发 |
| 响应式表单 | 基于模型,灵活可控 | 复杂表单、动态表单 |
二、基础配置 #
2.1 导入FormsModule #
typescript
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [FormsModule],
template: `...`
})
export class UserFormComponent {}
三、基本表单 #
3.1 简单表单 #
typescript
@Component({
selector: 'app-user-form',
standalone: true,
imports: [FormsModule],
template: `
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<div>
<label for="name">姓名:</label>
<input
type="text"
id="name"
name="name"
[(ngModel)]="user.name"
/>
</div>
<div>
<label for="email">邮箱:</label>
<input
type="email"
id="email"
name="email"
[(ngModel)]="user.email"
/>
</div>
<button type="submit">提交</button>
</form>
`
})
export class UserFormComponent {
user = {
name: '',
email: ''
};
onSubmit(form: NgForm) {
console.log('表单值:', form.value);
console.log('用户数据:', this.user);
}
}
3.2 ngForm指令 #
html
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<!-- 表单内容 -->
</form>
NgForm属性:
| 属性 | 说明 |
|---|---|
value |
表单值对象 |
valid |
表单是否有效 |
invalid |
表单是否无效 |
pristine |
表单是否未被修改 |
dirty |
表单是否已被修改 |
touched |
表单是否被触摸 |
untouched |
表单未被触摸 |
3.3 ngModel指令 #
html
<!-- 基本用法 -->
<input [(ngModel)]="user.name" name="name" />
<!-- 添加ngModel以参与表单验证 -->
<input [(ngModel)]="user.name" name="name" ngModel />
<!-- 单向绑定 -->
<input [ngModel]="user.name" name="name" />
<input (ngModelChange)="onNameChange($event)" name="name" />
四、表单验证 #
4.1 内置验证器 #
html
<form #userForm="ngForm">
<!-- 必填验证 -->
<input
type="text"
name="name"
[(ngModel)]="user.name"
required
#nameInput="ngModel"
/>
<!-- 最小长度 -->
<input
type="text"
name="username"
[(ngModel)]="user.username"
required
minlength="3"
#usernameInput="ngModel"
/>
<!-- 最大长度 -->
<input
type="text"
name="bio"
[(ngModel)]="user.bio"
maxlength="200"
#bioInput="ngModel"
/>
<!-- 邮箱验证 -->
<input
type="email"
name="email"
[(ngModel)]="user.email"
required
email
#emailInput="ngModel"
/>
<!-- 最小值/最大值 -->
<input
type="number"
name="age"
[(ngModel)]="user.age"
min="18"
max="100"
#ageInput="ngModel"
/>
<!-- 正则验证 -->
<input
type="text"
name="phone"
[(ngModel)]="user.phone"
pattern="[0-9]{11}"
#phoneInput="ngModel"
/>
</form>
4.2 显示验证错误 #
html
<form #userForm="ngForm">
<div>
<label>姓名:</label>
<input
type="text"
name="name"
[(ngModel)]="user.name"
required
minlength="2"
#nameInput="ngModel"
/>
<!-- 显示错误 -->
<div *ngIf="nameInput.invalid && nameInput.touched">
<span *ngIf="nameInput.errors?.['required']">
姓名是必填项
</span>
<span *ngIf="nameInput.errors?.['minlength']">
姓名至少需要2个字符
</span>
</div>
</div>
</form>
4.3 验证状态样式 #
html
<style>
input.ng-valid.ng-touched {
border-color: green;
}
input.ng-invalid.ng-touched {
border-color: red;
}
.error-message {
color: red;
font-size: 12px;
}
</style>
<form #userForm="ngForm">
<input
type="text"
name="name"
[(ngModel)]="user.name"
required
#nameInput="ngModel"
[class.is-valid]="nameInput.valid && nameInput.touched"
[class.is-invalid]="nameInput.invalid && nameInput.touched"
/>
</form>
五、完整表单示例 #
typescript
import { Component } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-registration-form',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<form #registrationForm="ngForm" (ngSubmit)="onSubmit(registrationForm)">
<h2>用户注册</h2>
<!-- 用户名 -->
<div class="form-group">
<label for="username">用户名 *</label>
<input
type="text"
id="username"
name="username"
[(ngModel)]="user.username"
required
minlength="3"
maxlength="20"
#usernameInput="ngModel"
/>
<div class="error" *ngIf="usernameInput.invalid && usernameInput.touched">
<span *ngIf="usernameInput.errors?.['required']">用户名是必填项</span>
<span *ngIf="usernameInput.errors?.['minlength']">
用户名至少3个字符
</span>
</div>
</div>
<!-- 邮箱 -->
<div class="form-group">
<label for="email">邮箱 *</label>
<input
type="email"
id="email"
name="email"
[(ngModel)]="user.email"
required
email
#emailInput="ngModel"
/>
<div class="error" *ngIf="emailInput.invalid && emailInput.touched">
<span *ngIf="emailInput.errors?.['required']">邮箱是必填项</span>
<span *ngIf="emailInput.errors?.['email']">请输入有效的邮箱地址</span>
</div>
</div>
<!-- 密码 -->
<div class="form-group">
<label for="password">密码 *</label>
<input
type="password"
id="password"
name="password"
[(ngModel)]="user.password"
required
minlength="8"
#passwordInput="ngModel"
/>
<div class="error" *ngIf="passwordInput.invalid && passwordInput.touched">
<span *ngIf="passwordInput.errors?.['required']">密码是必填项</span>
<span *ngIf="passwordInput.errors?.['minlength']">
密码至少8个字符
</span>
</div>
</div>
<!-- 确认密码 -->
<div class="form-group">
<label for="confirmPassword">确认密码 *</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
[(ngModel)]="user.confirmPassword"
required
#confirmPasswordInput="ngModel"
/>
<div class="error" *ngIf="confirmPasswordInput.touched">
<span *ngIf="confirmPasswordInput.errors?.['required']">
请确认密码
</span>
<span *ngIf="user.password !== user.confirmPassword && !confirmPasswordInput.errors?.['required']">
两次密码不一致
</span>
</div>
</div>
<!-- 年龄 -->
<div class="form-group">
<label for="age">年龄</label>
<input
type="number"
id="age"
name="age"
[(ngModel)]="user.age"
min="18"
max="100"
#ageInput="ngModel"
/>
<div class="error" *ngIf="ageInput.invalid && ageInput.touched">
<span *ngIf="ageInput.errors?.['min']">年龄不能小于18岁</span>
<span *ngIf="ageInput.errors?.['max']">年龄不能超过100岁</span>
</div>
</div>
<!-- 性别 -->
<div class="form-group">
<label>性别</label>
<div>
<label>
<input
type="radio"
name="gender"
value="male"
[(ngModel)]="user.gender"
/> 男
</label>
<label>
<input
type="radio"
name="gender"
value="female"
[(ngModel)]="user.gender"
/> 女
</label>
</div>
</div>
<!-- 爱好 -->
<div class="form-group">
<label>爱好</label>
<div>
<label *ngFor="let hobby of hobbies">
<input
type="checkbox"
name="hobbies"
[value]="hobby"
[(ngModel)]="user.hobbies"
/> {{ hobby }}
</label>
</div>
</div>
<!-- 城市 -->
<div class="form-group">
<label for="city">城市</label>
<select
id="city"
name="city"
[(ngModel)]="user.city"
required
#cityInput="ngModel"
>
<option value="">请选择</option>
<option *ngFor="let city of cities" [value]="city">
{{ city }}
</option>
</select>
<div class="error" *ngIf="cityInput.invalid && cityInput.touched">
<span *ngIf="cityInput.errors?.['required']">请选择城市</span>
</div>
</div>
<!-- 简介 -->
<div class="form-group">
<label for="bio">简介</label>
<textarea
id="bio"
name="bio"
[(ngModel)]="user.bio"
maxlength="200"
#bioInput="ngModel"
></textarea>
<div class="hint">{{ user.bio?.length || 0 }}/200</div>
</div>
<!-- 同意条款 -->
<div class="form-group">
<label>
<input
type="checkbox"
name="agree"
[(ngModel)]="user.agree"
required
#agreeInput="ngModel"
/> 我同意用户条款
</label>
<div class="error" *ngIf="agreeInput.invalid && agreeInput.touched">
<span>请同意用户条款</span>
</div>
</div>
<!-- 提交按钮 -->
<button
type="submit"
[disabled]="registrationForm.invalid">
注册
</button>
<!-- 表单状态 -->
<div class="form-status">
<p>表单状态:</p>
<p>有效: {{ registrationForm.valid }}</p>
<p>已修改: {{ registrationForm.dirty }}</p>
<p>已触摸: {{ registrationForm.touched }}</p>
</div>
</form>
`,
styles: [`
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; }
.form-group input, .form-group select, .form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.error { color: red; font-size: 12px; margin-top: 5px; }
.hint { color: #666; font-size: 12px; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; }
button:disabled { background: #ccc; }
`]
})
export class RegistrationFormComponent {
user = {
username: '',
email: '',
password: '',
confirmPassword: '',
age: null,
gender: '',
hobbies: [],
city: '',
bio: '',
agree: false
};
hobbies = ['阅读', '运动', '音乐', '旅行', '游戏'];
cities = ['北京', '上海', '广州', '深圳', '杭州'];
onSubmit(form: NgForm) {
if (form.valid) {
console.log('表单提交:', form.value);
console.log('用户数据:', this.user);
}
}
}
六、ngModelGroup #
6.1 分组表单 #
html
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<!-- 地址分组 -->
<div ngModelGroup="address" #addressGroup="ngModelGroup">
<h3>地址信息</h3>
<div>
<label>省份:</label>
<input
type="text"
name="province"
[(ngModel)]="user.address.province"
required
/>
</div>
<div>
<label>城市:</label>
<input
type="text"
name="city"
[(ngModel)]="user.address.city"
required
/>
</div>
<div>
<label>详细地址:</label>
<input
type="text"
name="detail"
[(ngModel)]="user.address.detail"
/>
</div>
<div *ngIf="addressGroup.invalid && addressGroup.touched">
请填写完整的地址信息
</div>
</div>
<button type="submit">提交</button>
</form>
6.2 表单值结构 #
typescript
// 表单值
{
address: {
province: '北京',
city: '北京',
detail: '朝阳区xxx'
}
}
七、动态表单 #
7.1 动态添加表单控件 #
typescript
@Component({
selector: 'app-dynamic-form',
template: `
<form #form="ngForm" (ngSubmit)="onSubmit(form)">
<div *ngFor="let field of fields; let i = index">
<label>{{ field.label }}:</label>
<input
[type]="field.type"
[name]="field.name"
[(ngModel)]="model[field.name]"
[required]="field.required"
/>
<button type="button" (click)="removeField(i)">删除</button>
</div>
<button type="button" (click)="addField()">添加字段</button>
<button type="submit">提交</button>
</form>
`
})
export class DynamicFormComponent {
fields = [
{ name: 'name', label: '姓名', type: 'text', required: true },
{ name: 'email', label: '邮箱', type: 'email', required: true }
];
model: any = {};
addField() {
const index = this.fields.length + 1;
this.fields.push({
name: `field${index}`,
label: `字段${index}`,
type: 'text',
required: false
});
}
removeField(index: number) {
const fieldName = this.fields[index].name;
delete this.model[fieldName];
this.fields.splice(index, 1);
}
onSubmit(form: NgForm) {
console.log(form.value);
}
}
八、表单重置 #
8.1 重置表单 #
typescript
@Component({
template: `
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<!-- 表单内容 -->
<button type="submit">提交</button>
<button type="button" (click)="resetForm(userForm)">重置</button>
</form>
`
})
export class UserFormComponent {
user = { name: '', email: '' };
onSubmit(form: NgForm) {
console.log(form.value);
this.resetForm(form);
}
resetForm(form: NgForm) {
form.reset();
// 或重置为特定值
// form.resetForm({ name: '', email: '' });
}
}
九、模板驱动表单最佳实践 #
9.1 适用场景 #
- 简单表单
- 快速原型开发
- 表单逻辑简单
9.2 注意事项 #
typescript
// 推荐:使用独立导入
@Component({
imports: [FormsModule]
})
// 为每个输入添加name属性
<input name="fieldName" [(ngModel)]="value" />
// 使用模板引用变量访问验证状态
<input #inputRef="ngModel" />
// 提交前检查表单有效性
onSubmit(form: NgForm) {
if (form.valid) {
// 提交逻辑
}
}
十、总结 #
| 概念 | 说明 |
|---|---|
ngForm |
表单指令,管理整个表单 |
ngModel |
双向数据绑定 |
ngModelGroup |
表单分组 |
| 验证器 | required, minlength, maxlength, email, pattern, min, max |
| 验证状态 | valid, invalid, touched, untouched, dirty, pristine |
下一步:响应式表单
最后更新:2026-03-26