# [(ngModel)]
用表单处理用户输入是许多常见应用的基础功能。 应用通过表单来让用户登录、修改个人档案、输入敏感信息以及执行各种数据输入任务。
Angular 提供了两种不同的方法来通过表单处理用户输入:响应式表单和模板驱动表单。 在此我们学习在生产项目使用频率较**低**的模板驱动表单。
> [info] 除特殊说明外,本节代码操作均位于`src/app/add`文件夹。
## C层初始化
首先,我们在C层中初始化一个教师,打开`app.component.ts`:
```typescript
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-add',
templateUrl: './add.component.html',
styleUrls: ['./add.component.css']
})
export class AddComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
```
如下初始化教师teacher:
```typescript
export class AddComponent implements OnInit {
teacher = {
name: '',
username: '',
email: '',
sex: true
};
constructor() {
}
ngOnInit(): void {
}
}
```
接下来,定义一个当用户点击『保存』按钮时执行的方法,通常来讲,如果C层中的某个方法的作用是被V层的某个动作(我们把用户点击『保存』按钮称为一个动作)触发,则该方法名将以`on`打头,比如此时我们起名为`onSubmit()`,作用为在控制台中打印当前教师的值:
```typescript
ngOnInit(): void {
}
onSubmit(): void {
console.log(this.teacher);
}
}
```
## V层绑定方法
在angular中,当表单被提交时,将触发`form`元素的 `ngSubmit`方法,我们如下将`ngSubmit`绑定到C层的`onSubmit()`方法上:
```html
<form class="container-sm" (ngSubmit)="onSubmit()">
```
细心的你可能也注意到了,聪明的webstrom编辑器在此提示了一个错误:
![image-20210224211933920](https://img.kancloud.cn/a3/e9/a3e9f8dd262dd4a43ca84942882ac883_1094x138.png)
这是由于angular虽然内置了对`ngSubmit`方法的支持,但却并未默认开启。为了达到高度的可定制化,angular把一些内置的功能分别放到了内置的不同模块中。而若想使这些功能生效,则需要在当前模块中引入(imports)这些带有功能的模块。在angular中,对`ngSubmit`方法的支持模块名为:`FormsModule`。
我们打开当前组件所在模块`AppModule`对应的文件`src/app/app.module.ts`,并将`FormsModule`添加到`imports`中:
```typescript
import {HttpClientModule} from '@angular/common/http';
import {AddComponent} from './add/add.component';
+import {FormsModule} from '@angular/forms';
imports: [
BrowserModule,
AppRoutingModule,
- HttpClientModule
+ HttpClientModule,
+ FormsModule
],
```
> [success] 本节开始,我们将使用国际通用的文件比较方法来展示文件的变化情况。`-`代表原文件本行删除,`+`代表在原文件此行上新增。
此时webstrom对应的错误消失:
![image-20210224212719979](https://img.kancloud.cn/e8/20/e82041e5895dff36541c52b222103643_889x84.png)
如果接下来,我们还需要将`FormsModule`添加到当前的单元测试文件中,以使其在当前动态模块下生效:
```
👉 import {FormsModule} from '@angular/forms';
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AddComponent],
imports: [FormsModule] ✚
})
.compileComponents();
});
```
- 👉 在后续教程中,我们将逐步省略**非**首次引入代码。
此时当我们在V层中点击『保存』按钮时,C层中的`onSubmit()`将被执行,控制台打印信息如下:
![image-20210224213859283](https://img.kancloud.cn/8f/64/8f6495e127d611b8f1d2b039c949d74c_702x193.png)
## V层绑定数据
模板驱动表单提供了一种非常简单的数据绑定方式 ---- `[(ngModel)]="xxx"`,比如我们可以使用如下代码将C层teacher中的属性快速的绑定到V层中:
```html
<div class="col-sm-10">
- <input type="text" class="form-control" id="name">
+ <input type="text" class="form-control" ➊name="name" [(ngModel)]="teacher.name" id="name">
</div>
</div>
<div class="mb-3 row">
<label for="username" class="col-sm-2 col-form-label">用户名</label>
<div class="col-sm-10">
- <input type="text" class="form-control" id="username">
+ <input type="text" class="form-control" ➊name="username" [(ngModel)]="teacher.username" id="username">
</div>
</div>
<div class="mb-3 row">
<label for="email" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
- <input type="text" class="form-control" id="email">
+ <input type="text" class="form-control" ➊name="email" [(ngModel)]="teacher.email" id="email">
</div>
```
**注意:** ➊ 我们在为input添加`[(ngModel)]`的同时,还必须为其增加`name`属性。
此时我们为表单加入相关测试信息后点击『保存』按钮,将在控制台中打印如下信息,这充分的说明用户在V层中输入的数据成功的传入了C层。
![image-20210224220547140](https://img.kancloud.cn/0b/27/0b279531c87f3a7a5ab09a9bc9dd4db5_806x391.png)
由于我们在性别中需要接收一个`boolean`类型的值,所以性别所使用的`radio`的处理的稍微麻烦一些:
```html
<div class="form-check form-check-inline">
- <input class="form-check-input" type="radio" name="inlineRadioOptions" id="sex-male" value="option1">
+ <input class="form-check-input" type="radio" id="sex-male"
+ name="sex" ➊
+ [(ngModel)]="teacher.sex" ➋
+ [value]="true" ➌>
<label class="form-check-label" for="sex-male">男</label>
</div>
<div class="form-check form-check-inline">
- <input class="form-check-input" type="radio" name="inlineRadioOptions" id="sex-female" value="option2">
+ <input class="form-check-input" type="radio" id="sex-female"
+ name="sex" ➊
+ [(ngModel)]="teacher.sex" ➋
+ [value]="false" ➌>
<label class="form-check-label" for="sex-female">女</label>
</div>
```
- ➊ 使用`[(ngModel)]`时必须设置`name`值,且两个radio的值必须一致
- ➋ 两个radio绑定同一值`teacher.sex`
- ➌ 此处使用的是`[value]`="true"而非`value="true"`
选择性别后,点击保存按钮在控制台中成功打印出了`sex`的值:
![image-20210224221618944](https://img.kancloud.cn/c8/56/c856faca05c6f7330036408681339798_875x255.png)
## 本节资源
| 名称 | 地址 | 备注 |
| -------------- | ------------------------------------------------------------ | ----------------------- |
| 建立响应式表单 | [https://angular.cn/guide/forms-overview#setup-in-reactive-forms](https://angular.cn/guide/forms-overview#setup-in-reactive-forms) | |
| 保存表单数据 | [https://angular.cn/guide/reactive-forms#grouping-form-controls](https://angular.cn/guide/reactive-forms#grouping-form-controls) | **保存表单数据** 一小节 |
| 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step2.3.2.zip](https://github.com/mengyunzhi/angular11-guild/archive/step2.3.2.zip) | |
- 序言
- 第一章 Hello World
- 1.1 环境安装
- 1.2 Hello Angular
- 1.3 Hello World!
- 第二章 教师管理
- 2.1 教师列表
- 2.1.1 初始化原型
- 2.1.2 组件生命周期之初始化
- 2.1.3 ngFor
- 2.1.4 ngIf、ngTemplate
- 2.1.5 引用 Bootstrap
- 2.2 请求后台数据
- 2.2.1 HttpClient
- 2.2.2 请求数据
- 2.2.3 模块与依赖注入
- 2.2.4 异步与回调函数
- 2.2.5 集成测试
- 2.2.6 本章小节
- 2.3 新增教师
- 2.3.1 组件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 对接后台
- 2.3.4 路由
- 2.4 编辑教师
- 2.4.1 组件初始化
- 2.4.2 获取路由参数
- 2.4.3 插值与模板表达式
- 2.4.4 初识泛型
- 2.4.5 更新教师
- 2.4.6 测试中的路由
- 2.5 删除教师
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome图标库
- 2.6.3 firefox
- 2.7 总结
- 第三章 用户登录
- 3.1 初识单元测试
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 着陆组件
- 3.5 @Output
- 3.6 TypeScript 类
- 3.7 浏览器缓存
- 3.8 总结
- 第四章 个人中心
- 4.1 原型
- 4.2 管道
- 4.3 对接后台
- 4.4 x-auth-token认证
- 4.5 拦截器
- 4.6 小结
- 第五章 系统菜单
- 5.1 延迟及测试
- 5.2 手动创建组件
- 5.3 隐藏测试信息
- 5.4 规划路由
- 5.5 定义菜单
- 5.6 注销
- 5.7 小结
- 第六章 班级管理
- 6.1 新增班级
- 6.1.1 组件初始化
- 6.1.2 MockApi 新建班级
- 6.1.3 ApiInterceptor
- 6.1.4 数据验证
- 6.1.5 教师选择列表
- 6.1.6 MockApi 教师列表
- 6.1.7 代码重构
- 6.1.8 小结
- 6.2 教师列表组件
- 6.2.1 初始化
- 6.2.2 响应式表单
- 6.2.3 getTestScheduler()
- 6.2.4 应用组件
- 6.2.5 小结
- 6.3 班级列表
- 6.3.1 原型设计
- 6.3.2 初始化分页
- 6.3.3 MockApi
- 6.3.4 静态分页
- 6.3.5 动态分页
- 6.3.6 @Input()
- 6.4 编辑班级
- 6.4.1 测试模块
- 6.4.2 响应式表单验证
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定义FormControl
- 6.4.6 代码重构
- 6.4.7 小结
- 6.5 删除班级
- 6.6 集成测试
- 6.6.1 惰性加载
- 6.6.2 API拦截器
- 6.6.3 路由与跳转
- 6.6.4 ngStyle
- 6.7 初识Service
- 6.7.1 catchError
- 6.7.2 单例服务
- 6.7.3 单元测试
- 6.8 小结
- 第七章 学生管理
- 7.1 班级列表组件
- 7.2 新增学生
- 7.2.1 exports
- 7.2.2 自定义验证器
- 7.2.3 异步验证器
- 7.2.4 再识DI
- 7.2.5 属性型指令
- 7.2.6 完成功能
- 7.2.7 小结
- 7.3 单元测试进阶
- 7.4 学生列表
- 7.4.1 JSON对象与对象
- 7.4.2 单元测试
- 7.4.3 分页模块
- 7.4.4 子组件测试
- 7.4.5 重构分页
- 7.5 删除学生
- 7.5.1 第三方dialog
- 7.5.2 批量删除
- 7.5.3 面向对象
- 7.6 集成测试
- 7.7 编辑学生
- 7.7.1 初始化
- 7.7.2 自定义provider
- 7.7.3 更新学生
- 7.7.4 集成测试
- 7.7.5 可订阅的路由参数
- 7.7.6 小结
- 7.8 总结
- 第八章 其它
- 8.1 打包构建
- 8.2 发布部署
- 第九章 总结