# Binding to user input events
在新增及编辑功能中,我们在`form 表单`中使用了`(ngSubmit)="onSubmit()"`来触发了C层的`onSubmit()`方法最终完成表单的提交。`(ngSubmit)="onSubmit()"`:当用户点击表单提交时,触发我们C层的`onSubmit()`方法。这个过程便是:` Binding to user input events 绑定到用户输入事件`。
用户在使用系统过程中与网页的互动,我们称为`event 事件`。`html event`分别五大类:`windows 事件`,比如我们对浏览器的窗口改变大小时会触发该事件;`Form 事件`,比如我们修改某个表单的值;`Keyboard 事件`,比如我们按下了某个按键;`Mouse 事件`,当我们点击鼠标时会触发;`Media 事件`,控制一些媒体的播放,比如`mp3`的播放、暂停。在`angular`中我们可以非常简单的使用内置的一些事件,比如使用`(ngSubmit)`来获取表单事件,使用`(click)`来获取点击事件。
## click
在教师列表中,为每一项添加一个删除按钮,当此按钮被点击时,触发C层的`onDelete`方法。
app.component.html
```
<td><a [routerLink]="['/edit', teacher.id]">编辑</a> <button (click)="onDelete()" ➊>删除</button></td>
```
* ➊ 添加按钮,并绑定`click`事件
app.component.ts
```
onDelete(): void {
console.log('click delete');
}
```
> 我们约定,与前台事件进行绑定的方法以`on`打头。比如`onSubmit`、`onDelete`。
## 测试
![](https://img.kancloud.cn/17/ab/17abb33af91bb913044d4a8f800fdf4a_701x169.png)
打开[http://localhost:4200/](http://localhost:4200/)及浏览器控制台,点击`删除`按钮后在控制台中查看信息,符合预期。
# 传入待删除对象
在事件中调用C层的方法时,还可以进行相应的传值。
```html
<td><a [routerLink]="['/edit', teacher.id]">编辑</a> <button (click)="onDelete(teacher)➊">删除</button></td>
```
* ➊ 向删除方法中传入当前行的对象`teacher`
```js
onDelete(teacher: any): void { ➊
console.log(teacher); ➋
}
```
* ➊ 在方法中增加参数`teacher`。
* ➋ 测试
## 测试
![](https://img.kancloud.cn/b1/e9/b1e98763cd1a438a172aeba68cffccd8_936x171.png)
# delete方法请求后台
同`get`方法的相同,我们直接调用`httpClient.delete(url: string)`方法来进行`delete`方法的请求.
```js
/**
* 点击删除按钮时删除对应的教师
* @param teacher 要删除的教师
*/
onDelete(teacher: any): void {
const url = 'http://localhost:8080/Teacher/' + teacher.id;
this.httpClient.delete(url)
.subscribe(() => {
console.log('删除成功');
this.ngOnInit(); ➊
}, () => {
console.log('删除失败');
});
}
```
* ➊ 删除成功后,重新加载数据。
## 测试
![](https://img.kancloud.cn/bd/2b/bd2ba4127c9544a3d4b524f1c7f6b683_428x36.png)
在开启后台的情况下,将出现`403`无此权限错误;在未开启后台的情况下,将出现`404`资源不存在错误。在未对接后台前,这都是正常的。
# TypeScript
`JavaScript`是个伟大、灵活的语言。同时它有具有着上手极简单、精通特别难的特点。这有些类似于网络游戏,刚刚接触的时候感觉没有什么进而会给你极其优越的成就感;但随着时间的推移总会出一现新奇的东西需要学习和掌握。当然了,对于我们而言便是一路踩坑、爬坑的经历。`TypeScript`及其类似的语言正是看到了这一点,使得新手在使用`JavaScript`能够少踩一些坑,使得习惯了使用不太灵活的面向对象的语言的开发人员能够快速开发`JavaScript`程序。其实本质上`TypeScript`并不是一门语言而一个解析器,它使用一些新的语法、规范来约束我们的开发,而最终交给浏览器去执行的仍然是 `JavaScript`,只不过这个 `JavaScript`是由`TypeScript`翻译后自动生成的。
官方如是说:**TypeScript is a superset of JavaScript that compiles to clean JavaScript output.**、**TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open source.**
如官方所说它是一个`typed superset 有类型的超集`。只所以这样说,是由于`javascript`其实是`untyped 弱类型`的语言。比如我们可以在`js`中这样定义`var a = 3; a = 'yunzhi'; `,这不会发生任何问题。但如果在`ts`中这样写:`let a = 3; a = 'yunzhi';`便会引发一个编译错误: `Error:(42, 5) TS2322: Type '"yunzhi"' is not assignable to type 'number'.`。这时由于`ts`是一种强类型语言,和其它的强类型语言一样,在`ts`中变量一旦规定了类型,那么在赋值时就必须与该类型相匹配。但由于`ts`的最终目标是翻译为`js`,同时呢在`ts`中我们还可以使用原有的一些`js`库,那么就需要有一个兼容性的问题,所以`ts`中使有了类型`any`。所以在`ts`中这样编写是没有问题的:`let a: any; a = 3; a = 'yunzhi';`。而`any`则代表任意类型。
## 语法较验
在实际的开发中,我们应该最大限度的去规避使用`any`类型,这是因为我们无法由`any`类型中得到任何的帮助信息。比如我们刚刚编写的:`onDelete(teacher: any): void`对于1个月后的自己,此函数等价于`onDelete(a: any): void`,而我们对`a`的值、类型一无所知。
而`typescript`有着强大的语法较验功能,该功能能够帮助我们规避一些拼写时的低端错误。比如我们可以在`onDelete`方法中注明该变量的类型或成功调用该方法该变量需要具有的类型。
```
onDelete(teacher: {id: number} ➊):void {
const url = 'http://localhost:8080/Teacher/' + teacher.id;
this.httpClient.delete(url)
.subscribe(() => {
console.log('删除成功');
this.ngOnInit();
}, () => {
console.log('删除失败');
});
}
```
* ➊ 接收的参数teacher的类型为`{} 对象`,且该`{} 对象`必须存在`id`属性,该属性的类型必须为`number`。
如此一来,如果在进行调用时传入的对象无`id`属性,或是`id`属性的类型不是`number`,`ts`在编译过程中便会发生错误来提醒我们此次的调用存在问题,从而避免了生产环境中的一些不可预测错误。同时其它的团队成员只要看到了该参数的定义便会清晰:该参数接收的对象中必须有`id`属性,且此属性的值的类型为`number`,在进行调用时需要遵循此规划来做。
在后续的教程中,我们将越来越多的`定义参数类型`的方法来避免语法错误。除此以外,这种写法还有一个好处是我们以后由于一些特定的不可控的事情导致需要重写该功能时,我们可以根据参数以及注释信息达到快速二次开发的目的。我们认为:软件在开发中需求必然会经过变更,而我们做的所有的规范都是为了满足需求的变更。我们希望随着项目的逐渐增大,尽量少的增加维护难度,最终达到易维护的系统。争取能够使得一个团队写的代码就像一个人写的一样,争取今天读一个月以前写的代码就像读昨天写的代码一样。把开发软件做成一个实实在在的工程,这可能就是`软件工程`名称的由来吧。
# 前后台对接总结
`delete`请求我们接触的第4个请求方法,除此以外我们还会接触到`options`及`patch`方法,我们来简单总结一下:
| 请求方法 | 前台方法 | 后台方法 | 适用场景 |
| --- | --- | --- | --- |
| get | httpClient.get(url: string) | @GetMapping | 按条件查询获取后台的所有数据 |
| get | httpClient.get(url: string) | @GetMapping("{id}") | 按条件查询获取后台的某条数据 |
| post | httpClient.post(url: string, data: Object) | @PostMapping, @RequestBody | 新增数据 |
| put | httpClient.put(url: string, data: Object) | @PutMapping("{id}") @RequestBody | 更新某条数据(一般用于更新实体的所有字段) |
| delete | httpClient.delete(url: string) | @DeleteMapping("id") | 删除某条数据 |
| options | - | 浏览器自动发生,用于确认某些资源的访问权限 |
| patch | httpClient.patch(url: string, data: Object) | @PatchMapping("{id}") @RequestBody | 更新某条数据(一般用于更新实体的部分字段) |
# 参考文档
| 名称 | 链接 | 预计学习时长(分) |
| --- | --- | --- |
| TypeScript(github) | [https://github.com/microsoft/TypeScript](https://github.com/microsoft/TypeScript) | 1 |
| TypeScript | [https://www.typescriptlang.org/](https://www.typescriptlang.org/) | 1 |
| TypeScript 基础类型 | [https://www.runoob.com/typescript/ts-type.html](https://www.runoob.com/typescript/ts-type.html) | 15 |
| TypeScript 函数 | [https://www.runoob.com/typescript/ts-function.html](https://www.runoob.com/typescript/ts-function.html) | 15 |
| 用户输入 | [https://www.angular.cn/guide/user-input](https://www.angular.cn/guide/user-input) | 10 |
| 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.5.1](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.5.1) | - |
- 序言
- 第一章:Hello World
- 第一节:Angular准备工作
- 1 Node.js
- 2 npm
- 3 WebStorm
- 第二节:Hello Angular
- 第三节:Spring Boot准备工作
- 1 JDK
- 2 MAVEN
- 3 IDEA
- 第四节:Hello Spring Boot
- 1 Spring Initializr
- 2 Hello Spring Boot!
- 3 maven国内源配置
- 4 package与import
- 第五节:Hello Spring Boot + Angular
- 1 依赖注入【前】
- 2 HttpClient获取数据【前】
- 3 数据绑定【前】
- 4 回调函数【选学】
- 第二章 教师管理
- 第一节 数据库初始化
- 第二节 CRUD之R查数据
- 1 原型初始化【前】
- 2 连接数据库【后】
- 3 使用JDBC读取数据【后】
- 4 前后台对接
- 5 ng-if【前】
- 6 日期管道【前】
- 第三节 CRUD之C增数据
- 1 新建组件并映射路由【前】
- 2 模板驱动表单【前】
- 3 httpClient post请求【前】
- 4 保存数据【后】
- 5 组件间调用【前】
- 第四节 CRUD之U改数据
- 1 路由参数【前】
- 2 请求映射【后】
- 3 前后台对接【前】
- 4 更新数据【前】
- 5 更新某个教师【后】
- 6 路由器链接【前】
- 7 观察者模式【前】
- 第五节 CRUD之D删数据
- 1 绑定到用户输入事件【前】
- 2 删除某个教师【后】
- 第六节 代码重构
- 1 文件夹化【前】
- 2 优化交互体验【前】
- 3 相对与绝对地址【前】
- 第三章 班级管理
- 第一节 JPA初始化数据表
- 第二节 班级列表
- 1 新建模块【前】
- 2 初识单元测试【前】
- 3 初始化原型【前】
- 4 面向对象【前】
- 5 测试HTTP请求【前】
- 6 测试INPUT【前】
- 7 测试BUTTON【前】
- 8 @RequestParam【后】
- 9 Repository【后】
- 10 前后台对接【前】
- 第三节 新增班级
- 1 初始化【前】
- 2 响应式表单【前】
- 3 测试POST请求【前】
- 4 JPA插入数据【后】
- 5 单元测试【后】
- 6 惰性加载【前】
- 7 对接【前】
- 第四节 编辑班级
- 1 FormGroup【前】
- 2 x、[x]、{{x}}与(x)【前】
- 3 模拟路由服务【前】
- 4 测试间谍spy【前】
- 5 使用JPA更新数据【后】
- 6 分层开发【后】
- 7 前后台对接
- 8 深入imports【前】
- 9 深入exports【前】
- 第五节 选择教师组件
- 1 初始化【前】
- 2 动态数据绑定【前】
- 3 初识泛型
- 4 @Output()【前】
- 5 @Input()【前】
- 6 再识单元测试【前】
- 7 其它问题
- 第六节 删除班级
- 1 TDD【前】
- 2 TDD【后】
- 3 前后台对接
- 第四章 学生管理
- 第一节 引入Bootstrap【前】
- 第二节 NAV导航组件【前】
- 1 初始化
- 2 Bootstrap格式化
- 3 RouterLinkActive
- 第三节 footer组件【前】
- 第四节 欢迎界面【前】
- 第五节 新增学生
- 1 初始化【前】
- 2 选择班级组件【前】
- 3 复用选择组件【前】
- 4 完善功能【前】
- 5 MVC【前】
- 6 非NULL校验【后】
- 7 唯一性校验【后】
- 8 @PrePersist【后】
- 9 CM层开发【后】
- 10 集成测试
- 第六节 学生列表
- 1 分页【后】
- 2 HashMap与LinkedHashMap
- 3 初识综合查询【后】
- 4 综合查询进阶【后】
- 5 小试综合查询【后】
- 6 初始化【前】
- 7 M层【前】
- 8 单元测试与分页【前】
- 9 单选与多选【前】
- 10 集成测试
- 第七节 编辑学生
- 1 初始化【前】
- 2 嵌套组件测试【前】
- 3 功能开发【前】
- 4 JsonPath【后】
- 5 spyOn【后】
- 6 集成测试
- 7 @Input 异步传值【前】
- 8 值传递与引入传递
- 9 @PreUpdate【后】
- 10 表单验证【前】
- 第八节 删除学生
- 1 CSS选择器【前】
- 2 confirm【前】
- 3 功能开发与测试【后】
- 4 集成测试
- 5 定制提示框【前】
- 6 引入图标库【前】
- 第九节 集成测试
- 第五章 登录与注销
- 第一节:普通登录
- 1 原型【前】
- 2 功能设计【前】
- 3 功能设计【后】
- 4 应用登录组件【前】
- 5 注销【前】
- 6 保留登录状态【前】
- 第二节:你是谁
- 1 过滤器【后】
- 2 令牌机制【后】
- 3 装饰器模式【后】
- 4 拦截器【前】
- 5 RxJS操作符【前】
- 6 用户登录与注销【后】
- 7 个人中心【前】
- 8 拦截器【后】
- 9 集成测试
- 10 单例模式
- 第六章 课程管理
- 第一节 新增课程
- 1 初始化【前】
- 2 嵌套组件测试【前】
- 3 async管道【前】
- 4 优雅的测试【前】
- 5 功能开发【前】
- 6 实体监听器【后】
- 7 @ManyToMany【后】
- 8 集成测试【前】
- 9 异步验证器【前】
- 10 详解CORS【前】
- 第二节 课程列表
- 第三节 果断
- 1 初始化【前】
- 2 分页组件【前】
- 2 分页组件【前】
- 3 综合查询【前】
- 4 综合查询【后】
- 4 综合查询【后】
- 第节 班级列表
- 第节 教师列表
- 第节 编辑课程
- TODO返回机制【前】
- 4 弹出框组件【前】
- 5 多路由出口【前】
- 第节 删除课程
- 第七章 权限管理
- 第一节 AOP
- 总结
- 开发规范
- 备用