在前面的章节中,我们已经使用Angular成功的显示了Hello World!。下面,我们一起探索一下src下的app文件夹。
```
├── app
│ ├── app-routing.module.ts ❶
│ ├── app.component.html ❷ ❸
│ ├── app.component.sass ❷ ➍
│ ├── app.component.spec.ts ➎
│ ├── app.component.ts ❷ ➏
│ └── app.module.ts ➐
```
此文件夹中有6个文件组成,共分成3个大部分。
* ❶ 路由文件,负责数据转发。
* ❷ `app`组件。
* ❸ 组件视图(V层)。
* ➍ 组件样式(V层)。
* ➎ `app`组件对应的测试文件。
* ➏ 组件主体文件(C层)。
* ➐ `app`模块,巧的是它的名字与组件重名(仅仅是名字相同而已,就像我们学校有条路起名为逸夫,大家称它为逸夫路;我还学校有个图书馆,巧的是也叫逸夫,只不过它是逸夫楼一样)。
他们间大体的关系如下图示:
![](https://img.kancloud.cn/51/ad/51ad2ef3c040c838356398d93959e4cf_800x166.png)
# 模块
一个angular应用通常会有多个模块(Module)组成。angular中的模块共有9个配置部分。其中最常用的有4部分,笔者将其命名为:私有资源列表、公有资源列表、协作模块列表以及扩展功能列表。
![](https://img.kancloud.cn/ae/13/ae13e2ea67660d30ab526def84c9575f_572x349.png)
以`app`模块为例认识下模块的定义与基本组成:
app.module.ts
```typescript
import {BrowserModule} from '@angular/platform-browser'; ➊
import {NgModule} from '@angular/core'; ➊
import {AppRoutingModule} from './app-routing.module'; ➊
import {AppComponent} from './app.component'; ➊
@NgModule({ ➋
declarations: [ ➌
AppComponent ➍
],
imports: [ ➎
BrowserModule, ➏
AppRoutingModule, ➏
],
providers: [], ➐
bootstrap: [AppComponent] ➑
})
export class AppModule { ➒
}
```
* ➊ 与php中的use, java中的import相同
* ➋ Angular的注解,将`AppModule`声明为模块
* ➌ 声明当前模块中的私有资源列表
* ➍ 将`app`组件加入到`app`模块的私有资源列表
* ➎ 声明模块的协作模块列表
* ➏ 将浏览器模块加入到协作列表中,该模块能够提供的功能是:渲染html文件等。
* ➏ 将路由模块加入到协作列表中,该模块提供的功能由`app-routing.module.ts`中的`AppRouting`模块所决定。
* ➐ 扩展功能列表
* ➑ 启动组件,由于某些配置的原因,当程序运行时会自动启动该组件。
* ➒ 定义类名并使用`export`声明,表明该类可被`app.module.ts`以外的文件引用。
通过上面的解析可看到,使用`@NgModule`来声明某类为angular应用中的一个模块,使用`@NgModule`中配置相应的属性来对此模块进行配置。其中属性`declarations`用于声明私有资源列表,属性`imports`用于声明协作模块列表,属性`providers`用于声明扩展功能列表。
所以模块的组成也可以使用属性关键字进行如下描述:
![](https://img.kancloud.cn/ca/be/cabe3f0a0b01e0319c843473f0a76a90_566x353.png)
## import与imports
➊处的import与➎处的imports之间除了长的像以外,本质上并没有其它的任何关联。
import是typescript的语法,属于编程语言层面,表示由某个文件中引入某个类、接口、方法至此文件。与其它语言的命名空间极为类型,稍有不同的是typescript并不需要在文件中使用package或namespace的字样来规定命名空间,而是将文件的存储位置直接做为了命名空间来使用。
而imports是属于angular这个基于typescript开发而来的框架的`@NgModule`注解的一个属性。它的含意是声明angular中的模块引入的其它的模块。在angular应用中,功能的完成依赖模块间的协同作业,而imports的作用便是来声明某个模块需要的协同者。
# 打开项目
找到前面安装的WebStorm并运行,选择open并找到项目文件:
![](https://img.kancloud.cn/40/f0/40f026f51a1bda81329899ea25be84ef_490x310.png)
![](https://img.kancloud.cn/66/a6/66a6ec07ecaabcbe194b758bee80d98f_429x431.png)
稍等片刻,待WebStrom初始化完成。接着打开shell进入项目文件夹,并使用`ng serve --open`启动项目并自动打开浏览器。
# 依赖注入
在Anguar中发起网络请求需要借助`AppRoutingModule`中被声明公有资源列表的`HttpClient`。`angular`中的组件可以构造函数中声明自己所需要的依赖类型。angular在初始化该组件时,将尝试在组件所在模块中按构造函数中类型自动注入相关功能的实例。以`App`组件需要具有网络请求功能的`HttpClient`为例:
AppComponent.ts
```typescript
import { Component } from '@angular/core';
import {HttpClient} from '@angular/common/http';➊
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass']
})
export class AppComponent {
constructor(private httpClient: HttpClient) { ➋
console.log(httpClient);
}
title = 'hello-world';
}
```
➊ 使用import由`@angular/common/http`中引入`HttpClient`到此文件。
➋ 构造函数中声明依赖类型为HttpClient。
> 在后续的教程中将只给出文件名,当未对文件路径做特殊说明时,表示该文件相对于`src/app/`文件夹。比如:`AppComponent.ts`实际为`src/app/AppComponent.ts`。
此时我们刷新页面查看控制台,将得到如下错误:
![](https://img.kancloud.cn/6d/24/6d24d68fe22a911566f9ac9cb3211e14_772x86.png)
这是由于angular在尝试注入HttpClient的过程中发生了错误。angular能够成功完成HttpClient的注入,则`App`组件所在的`App`模块需要满足以下任一条件:
<hr>
✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦ **以下内容很重要,请牢记!** ✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
1. 在当前模块**私有资源列表**中能够找到`HttpClient`。
2. 当前模块的父模块的**私有资源列表**中能够找到`HttpClient`。
3. 在前模块**协作模块列表**中的任意模块的**公有资源列表**中找到`HttpClient`
4. 在前模块父模块的**协作模块列表**中的任意模块的**公有资源列表**中找到`HttpClient`
✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦ **以上内容很重要,请牢记!** ✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦✦
<hr>
当前`App`模块可如下可描述为:
![](https://img.kancloud.cn/a8/af/a8af3684a2117184ceaae033756721d6_516x377.png)
App模块在创建AppComponent时,由于在其私有资源中找到了AppComponent,近而进一步为其注入`HttpClient`。在注入`HttpClient`注入未成功进而发生错误。
![](https://img.kancloud.cn/d6/47/d647736feb47feb129927311b54d7cf9_804x374.png)
这当然也在控制台中报发下错误的原因:
```
RROR NullInjectorError: StaticInjectorError(AppModule)[AppComponent -> HttpClient]:
StaticInjectorError(Platform: core)[AppComponent -> HttpClient]:
NullInjectorError: No provider for HttpClient!
```
> 如果某种错误是第一次出现,出现后你完全没有任何解决它的思路,此时你绝对不应该去傻傻的看代码是不是哪有问题。你的正确操作应该是:翻译!
```
严重错误 空注入器错误:静态注入器错误(AppMoule上发生的[在向AppComponent注入HttpClient时]:
静态注入器错误(在Platform:core上发生的)[在向AppComponent注入HttpClient时]:
空注入器错误: 没有HttpCilent的提供者
```
出错的原因找到了,解决问题的方法也就随着有了。
![](https://img.kancloud.cn/39/76/39768a7f8846692d01573d1aacaba85a_808x449.png)
>[success] 在angular中,任何模块都属于Root模块的子模块。
对应代码如下:
app.module.ts
```typescript
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {HttpClientModule} from '@angular/common/http'; ➊
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule ➋
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
```
* ➊ 由@angular/common/http中引入HttpClientModule至此文件
* ➋ 在协作模块列表中加入可以提供`HttpClient`的`HttpClientModule`
# 测试
打开控制台(F12),查看测试消息如下:
![](https://img.kancloud.cn/76/d6/76d62c46f99b434da8905a73689e9581_674x393.png)
控制台中成功的打印了`HttpClient`对象的信息同时未发生任何错误,注入成功。下个小节中展示如何使用`HttpClient`发起网络请求。
## 生活与计算机
计算机来源于生活,现实生活中大概会有这样的场景:技术部门组织春游需要一辆巴士,向公司统一协调部门打电话告知其需求,即:我们需要一辆巴士,此外还需要配备一名司机。
![](https://img.kancloud.cn/af/e5/afe59c5538178762bb581e55cac6acda_190x115.png)
上面的需求翻译成代码语言如下:
```
export class Play {
constructor(private bus: Bus, private driver: Driver)
}
}
```
其实此时我们并不关心统一协调部门为我们提供的巴士是公司自有车辆还是其由外面租赁的,也不关心巴士的颜色是红的还是绿的,当然也不关心司机是男是女。我们关心是功能,即巴士有运载人员的功能,司机有驾驶巴士的功能。
统一协调部门得到这个需要以后,去调配巴士和司机,比如此时公司的资源是这样的:
![](https://img.kancloud.cn/a4/4c/a44cb7581f0f362c8aeb548664dd3581_556x332.png)
统一调度部门发现能够满足我们的需求,于是将巴士及能驾驶巴士的司机指派给我们。
![](https://img.kancloud.cn/04/b5/04b552ae35a1aa08a10f989995d6860b_547x472.png)
要知道并不是所有的公司都有拥有自有用车的,那么当这类公司中的内部部门向统一协调部分发送用车需求时,统一协调部门该怎么办呢?相信你早有了答案 ---- 找第三租车公司呀!
按上面的思路,假设这个公司的汽车服务需要借肋于租车公司、零食需要借助于超市、药品需要借助于药店,那么就会出现如下情况:
![](https://img.kancloud.cn/19/9b/199b63ebdaee93cc83239062e3ca7ecd_814x637.png)
当其它部门提出`汽车+司机+药品+零食`、`汽车+司机+药品`、`药品+零食`需求时,统一协调部门都是可以做到的。但如果有部门提出`飞机+零食`的需求时,统一协调部门就做不到了,此时统一协调部门就会报异常,因为他指不到`飞机`的供应商来满足其它部门的需求。
>[success] 在计算机的世界里,我们把这种借助统一协调部门来自动提供指定需求的方法称为**依赖注入 ---- Dependency injection**,也就是你在面试时可能会被问到的`DI`。
## 计算机与生活
在我们刚刚接触的Angular的世界里,`app module(模块)`就是这个公司下的统一协调部门,负责该公司一切资源的调配;`app component组件`则是提出需求的其它部门,提出需求的方法是通过构造函数来声明。
所以我们前面刚刚添加过的app组件:
AppComponet.ts
```
constructor(private httpClient: HttpClient) { ➊
console.log(httpClient);
}
```
➊ 找自己所在的module要`HttpClient`。
前面我们分析`AppMoudle`时讲过了:`AppComponent`属于`AppMoudle`。所以这里的`HttpClient`应该由`AppMoudle`来提供,但此时`AppMoudle`还没有提供`HttpClient`的能力,所以我们打开控制台后,就出现了:
有了以上的提示再加上我们前面对DI知识的学习,解决这个问题也就不困难了。
# 本节小测
✓ 控制台报错为:
```
RROR NullInjectorError: StaticInjectorError(AppModule)[AppComponent -> HttpClient]:
StaticInjectorError(Platform: core)[AppComponent -> HttpClient]:
NullInjectorError: No provider for HttpClient!
```
报错内容一直在说`HttpClient`错误,而在`AppModule`中却这样声明:
```
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule ➊
],
```
问题:为什么提示说缺少HttpClient,但我们却在此imports了`HttpClientModule`呢?
- 序言
- 第一章: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
- 总结
- 开发规范
- 备用