在前面的章节中,我们已经使用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`呢?