# 初始化组件
是的,所有的功能在开发前都离不开初始化的工作。只有当组件被初始化完成后,整个功能才能抽象变更为具体。团队内部以及我们与甲方的交流也因此会加的顺畅,这保证了我们将开发一个与甲方预期一致的系统。
## 新建模块
我们在第二章曾经对模块进行了如下总结:
![image-20210228173021966](https://img.kancloud.cn/c6/77/c6778e95733b9e82f3a492baa4baeb92_1410x542.png)
可见模块与模块间是通过`imports`来配合完成工作的。
在教师管理一章中,我们使用了Angular初始化的自带模块`AppModule`,本节我们初始化一个单独用于班级管理的模块`ClazzModule`,然后在此模块中添加教师管理中的新增、列表、编辑等组件。这样的做当前的好处是可以使原本越来越乱的目录结构变得稍微的清爽一些。
我们使用shell来到src/app文件夹,执行`ng g module clazz`命令来快速的创建一个模块:
```bash
panjie@panjies-Mac-Pro app % pwd
/Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app
panjie@panjies-Mac-Pro app % ng g module clazz
CREATE src/app/clazz/clazz.module.ts (191 bytes)
```
该命令将自动为我们在app文件夹下创建一个新的文件夹clazz,并在该文件夹下自动创建一个`clazz.module.ts`文件。
```bash
panjie@panjies-Mac-Pro app % tree clazz
clazz
└── clazz.module.ts
```
文件内容如下:
```typescript
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class ClazzModule {
}
```
如上所示,类`ClazzModule`是使用了注解`@NgModule` ,因此被Angular识别为`模块`。
## 新建组件
接着使用shell进入src/app/clazz文件夹,执行创建组件命令:
```bash
panjie@panjies-Mac-Pro clazz % pwd
/Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app/clazz
panjie@panjies-Mac-Pro clazz % ng g c add
CREATE src/app/clazz/add/add.component.css (0 bytes)
CREATE src/app/clazz/add/add.component.html (18 bytes)
CREATE src/app/clazz/add/add.component.spec.ts (605 bytes)
CREATE src/app/clazz/add/add.component.ts (263 bytes)
UPDATE src/app/clazz/clazz.module.ts (250 bytes)
```
**注意**:必须确保shell所在文件夹位置为src/app/clazz。
Angular聪明的在当前文件夹创建了班级add组件,并将其声明到了clazz.module.ts中,以表明班级add组件为班级模块的一部分。
### V层
模板代码如下:
```html
b/first-app/src/app/clazz/add/add.component.html
<form class="container-sm">
<div class="mb-3 row">
<label for="name" class="col-sm-2 col-form-label">名称</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="name">
</div>
</div>
<div class="mb-3 row">
<label for="teacher" class="col-sm-2 col-form-label">班主任</label>
<div class="col-sm-10">
<select id="teacher" class="form-control" >
<option>张三</option>
<option>李四</option>
</select>
</div>
</div>
<div class="mb-3 row">
<div class="col-sm-10 offset-2">
<button class="btn btn-primary">保存</button>
</div>
</div>
</form>
```
使用`ng t`后启用单元测试并查看效果如下:
![image-20210318100250221](https://img.kancloud.cn/2b/f8/2bf859a8d1b3a66075b51eada441a5ad_1518x386.png)
### CV交互
select选择框如何做我们还不太清楚,但部分的功能我们还是学习过的,让我们简单来实现这些功能。在CV的交互中,至于先写哪个后写哪个完全看自己的习惯,喜欢先写哪个都可以,比如我在这比较喜欢先写V层:
```html
+++ b/first-app/src/app/clazz/add/add.component.html
@@ -1,14 +1,15 @@
-<form class="container-sm">
+<pre>{{clazz | json}}</pre> 👈
+<form class="container-sm" (ngSubmit)="onSubmit()">
<div class="mb-3 row">
<label for="name" class="col-sm-2 col-form-label">名称</label>
<div class="col-sm-10">
- <input type="text" class="form-control" name="name" id="name">
+ <input type="text" class="form-control" [(ngModel)]="clazz.name" name="name" id="name">
</div>
</div>
<div class="mb-3 row">
<label for="teacher" class="col-sm-2 col-form-label">班主任</label>
<div class="col-sm-10">
- <select id="teacher" class="form-control" >
+ <select id="teacher" class="form-control">
<option>张三</option>
<option>李四</option>
</select>
```
在这增加一个`<pre>`标签会有意想不到的收获。
再写C层:
```typescript
+++ b/first-app/src/app/clazz/add/add.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-add',
@@ -6,10 +6,17 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./add.component.css']
})
export class AddComponent implements OnInit {
+ clazz = {
+ name: ''
+ };
- constructor() { }
+ constructor() {
+ }
ngOnInit(): void {
}
+ onSubmit(): void {
+ console.log('submit');
+ }
}
```
这时候聪明的WebStorm将会在V层给我们一些错误的提示:
![image-20210318102928616](https://img.kancloud.cn/22/88/228847d93dc0565616ace4837588a522_1744x318.png)
![image-20210318102943663](https://img.kancloud.cn/63/f9/63f9c76628747431e89abca17e15e0c8_1492x230.png)
错误的意思都是在说没有任何的`directive`来支持我们刚刚写的代码,比如:`(ngSubmit)`以及`[(ngModel)]`。事实也是这样,由于`(ngSubmit)`以及`[(ngModel)]`并不存在于html语言中,所以要想使用其生效,则需要有一个叫做`directive`的东西来支持这样功能。而前我们提及过的`FromModule`便是能提供支持`(ngSubmit)`以及`[(ngModel)]`的模块。
模块与模块的交流位于模块的`imports`,为些我们将`FormModule`添加到当前组件所在的ClazzModule中:
```typescript
--- a/first-app/src/app/clazz/clazz.module.ts
+++ b/first-app/src/app/clazz/clazz.module.ts
@@ -1,12 +1,14 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import { AddComponent } from './add/add.component';
+import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [AddComponent],
imports: [
- CommonModule
+ CommonModule,
+ FormsModule
]
})
export class ClazzModule {
```
此时V层的错误消失。其实此时`ng t`所依赖的动态测试模块也面临着这个错误,只是WebStorme还没有智能到能检测出该错误,所以我们手动添加一下:
```typescript
--- a/first-app/src/app/clazz/clazz.module.ts
+++ b/first-app/src/app/clazz/clazz.module.ts
@@ -1,12 +1,14 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import { AddComponent } from './add/add.component';
+import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [AddComponent],
imports: [
- CommonModule
+ CommonModule,
+ FormsModule
]
})
export class ClazzModule {
```
至此大多数的初始化工作,就完成了。我们最后在单元测试中加入**自动检测变更**的代码后,当前的组件便会在单元测试中动起来了。
## Select
当领导给我们分配一些新的任务时,**我不会**三个字永远不要轻易的就说出口。在现有水平的基础上,合理的利用网上的学习资源,绝对能解决大多数的问题。即使最后没有得到解决的答案,基于当前自己找到的资源去和领导沟通也远胜过**我不会**三个字。
虽然我们还没有学习过如何使用Select,但我相信你当前一定有能力解决这个问题。Select使用方式与普通的input非常的相似:在元素上增加`name`属性并直接应用`([ngModel])`:
```html
+++ b/first-app/src/app/clazz/add/add.component.html
@@ -9,9 +9,11 @@
<div class="mb-3 row">
<label for="teacher" class="col-sm-2 col-form-label">班主任</label>
<div class="col-sm-10">
- <select id="teacher" class="form-control">
- <option>张三</option>
- <option>李四</option>
+ <select id="teacher" class="form-control"
+ name="teacher"
+ [(ngModel)]="clazz.teacherId">
+ <option [ngValue]="1">张三</option> 👈
+ <option [ngValue]="2">李四</option>
</select>
</div>
</div>
```
**注意**:这里使用`[ngValue]`而非`value`,在angular中`[xx]="yyy"`中的yyy表示typescript中的变量或值,我们在这里需要的值是number类型的`1`, `2`,而非字符串类型的`'1'`,`'2'`。如果你学过一些数据库的相关知道,一定知道数字1,2,3与字符1,2,3的区别。如果使用的是value而非`[ngValue]`,则将得到字符串`1`,`2`。
接着在C层中同步为clazz添加一个teacherId字段:
```typescript
+++ b/first-app/src/app/clazz/add/add.component.ts
@@ -7,7 +7,8 @@ import {Component, OnInit} from '@angular/core';
})
export class AddComponent implements OnInit {
clazz = {
- name: ''
+ name: '',
+ teacherId: null as unknown as number
};
constructor() {
```
测试如下:
![image-20210318110323682](https://img.kancloud.cn/c0/9d/c09d754cd42157c3ba1634737608974c_1574x520.png)
如果我们在`option`元素上,使用了`value`而非`[ngValue]`,则将得到字符类型的`'1'`:
![image-20210318110637762](https://img.kancloud.cn/4c/1f/4c1f10378e055e7ecec5ec918214f2e3_1536x418.png)
而这并不是我们想要的。
## 本节作业
新增班级的API为:
```bash
POST /clazz
```
| **类型Type** | **名称Name** | **描述Description** | **类型Schema** |
| :----------- | :----------- | :------------------ | :----------------------------------------------------------- |
| Body | clazz | 班级 | `{name: string, teacher: {id: number}}` |
| Response | | 响应 | `{id: number, name: string, createTime: number, teacher: {id: number, name: string}}` |
请求Body`{name: string, teacher: {id: number}}`中的teacher属性的类型是一个对象,该对象有一个id属性,类型为number。在响应信息中,给出了保存后的班级ID,以及班级对应的teacher对象基本信息。
1. 请尝试在`ng t`的情况下完成它。
2. 以下信息可能会给问题的解决带来一些思路:
1. `ng t`下报401的错误原因为何?
2. 如何在新建班级前完成用户的登录?
| 名称 | 链接 |
| ---------------------- | ------------------------------------------------------------ |
| 本节源码(包含本节答案) | [https://github.com/mengyunzhi/angular11-guild/archive/step6.1.1.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.1.1.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 发布部署
- 第九章 总结