## 环境变量 为了适应不同的环境,Angular提供了环境变量的概念。具体而言便是可以在开发的时候将变量设置为一个值,而在最后打包成生产代码的时候将变量再设置为另一个值。 比如我们在开发时将每页大小设置成了20了,而分页组件将位于表格的最小方,这样一来每次代码变更、浏览器自动刷新后我们都需要手动的将页面拉到最下方。对于此情此景,相信你更愿意在开发时候将每页大小设置为5,同时在项目打包上线时每页大小被自动设置为20。 `src`文件夹下有个子文件夹`environments`便是为了解决以下需要而存在的: ```bash panjie@panjies-iMac src % tree -L 1 . ├── app ├── assets ├── environments 👈 ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── styles.test.css └── test.ts ``` 该文件夹上有两个文件: ```bash panjie@panjies-iMac src % tree environments environments ├── environment.prod.ts └── environment.ts 0 directories, 2 files ``` 分别为最后打包上线时(以下简称生产环境)使用的`environment.prod.ts`以及开发时使用的`environment.ts`,预想在生产环境中将size配置为20,而在开发中将size配置为5,则仅需要在上述两个文件中分别声明size: ```typescript +++ b/first-app/src/environments/environment.prod.ts @@ -1,3 +1,4 @@ export const environment = { - production: true + production: true, + size: 20 }; ``` ```typescript +++ b/first-app/src/environments/environment.ts @@ -3,7 +3,8 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + size: 5 }; ``` 接下来在组件中使用环境变量中的size来初始化组件中的size值: ```typescript +++ b/first-app/src/app/student/student.component.ts @@ -2,6 +2,7 @@ import {Component, OnInit} from '@angular/core'; import {Page} from '../entity/page'; import {Student} from '../entity/student'; import {StudentService} from '../service/student.service'; +import {environment} from '../../environments/environment'; 👈 @Component({ selector: 'app-student', @@ -11,7 +12,7 @@ import {StudentService} from '../service/student.service'; export class StudentComponent implements OnInit { pageData = {} as Page<Student>; page = 0; - size = 20; + size = environment.size; constructor(private studentService: StudentService) { } ``` **注意:**environment的引用位置为`environments/environment`,而**不**是`environments/environment.prod` 此时在开发过程中,每页大小为5,而在生产环境中每页大小将为20(将在后面的章节中展示)。 ![image-20210419104719145](https://img.kancloud.cn/50/0e/500e1bdc9fe36badb93790f422837755_1916x848.png) 接下来我们引用在班级列表中使用过的分页模块。 ## 分页模块 班级列表中,我们已经打造过了一个可用的分页组件。这时候我们首先想到的应该是如何去复用这个分页组件,而不是参考前面的分页组件再制造一个一模一样的组件。在引入分页组件前,还需要简单的复习一下7.2.1小节中我们是如何在学生添加组件中使用了班级选择组件的: ![image-20210412144647751](https://img.kancloud.cn/5b/26/5b261d301d17edd064d3b1d5a76007fd_1390x426.png) 参考上图,可得如果在当前学生列表组件中使用分页组件,则需要打造一个如下图一样的关系图: ![image-20210419105514981](https://img.kancloud.cn/f6/97/f6975ff32ec1ccbf0016abbf771e2773_1264x372.png) 按上图的逻辑,固然能够满足当下需求。但也会面临一下问题:在`import`班级模块的时候,一些赠品(其它班级模块中的组件、指令、管道等)也会随之被引入,这与Angular模块化、惰性加载(按需加载)的思想相违背。 > Angular是个伟大的框架,革命性的完成了模块化、惰性加载的特性,做为对框架的尊重,我们也要充分的利用好Angular的伟大特性。 ![image-20210419110145591](https://img.kancloud.cn/c9/d6/c9d657a662904845b8dc08bbbe70f832_1194x398.png) 既然模块化能够解决按需加载的问题,那么再新建一个模块好了。 ![image-20210419110606318](https://img.kancloud.cn/be/ba/beba4dc45eae7c77ac21e76908933b8a_1634x378.png) 逻辑有了,实现便成了最简单的事情。 ### 新建模块 来到`/src/app/clazz/page`文件夹,使用`ng g m page --flat`命令创建`PageModule`: ```bash panjie@panjies-iMac page % pwd /Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app/clazz/page panjie@panjies-iMac page % ng g m page --flat CREATE src/app/clazz/page/page.module.ts (190 bytes) ``` 然后在该模块中添加`PageComponent`被将其声明为公有: ```typescript import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {PageComponent} from './page.component'; @NgModule({ declarations: [PageComponent], imports: [ CommonModule ], exports: [PageComponent] }) export class PageModule { } ``` 由于组件在一个作用域内仅能够声明在一个模块中,所以此时需要删除原班级模块中对Page组件的声明。同时为了继续在班级模块中使用分页组件,还需要在班级模块中使用import的方式来引入Page模块。 班级模块变动如下: ```typescript +++ b/first-app/src/app/clazz/clazz.module.ts @@ -7,6 +7,7 @@ import {ClazzComponent} from './clazz.component'; import {PageComponent} from './page/page.component'; import {EditComponent} from './edit/edit.component'; import {RouterModule, Routes} from '@angular/router'; +import {PageModule} from './page/page.module'; const routes: Routes = [ { @@ -24,10 +25,11 @@ const routes: Routes = [ ]; @NgModule({ - declarations: [AddComponent, KlassSelectComponent, ClazzComponent, PageComponent, EditComponent], + declarations: [AddComponent, KlassSelectComponent, ClazzComponent, EditComponent], imports: [ CommonModule, FormsModule, + PageModule, ReactiveFormsModule, RouterModule.forChild(routes) ] ``` 动态测试模块变动如下: ```typescript +++ b/first-app/src/app/clazz/clazz.component.spec.ts @@ -15,9 +16,10 @@ describe('ClazzComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ClazzComponent, PageComponent], + declarations: [ClazzComponent], imports: [ HttpClientModule, + PageModule, RouterTestingModule, MockApiTestingModule ] ``` 移除所有的`fit`后进行全局的单元测试: ![image-20210522084650488](https://img.kancloud.cn/31/a8/31a8f6973622432b02acaf4f9238ff62_1792x302.png) 单元测试通过证明了我们当前代码重构的有效性,下面便可以大胆放心的完成其它功能了。我们恢复当前组件测试用例中的`fit`继续开发。 ## 引入模块 ![image-20210419110606318](https://img.kancloud.cn/be/ba/beba4dc45eae7c77ac21e76908933b8a_1634x378.png) 最后按上图将分页模块引入动态测试模块: ```typescript +++ b/first-app/src/app/student/student.component.spec.ts @@ -5,6 +5,7 @@ import {RouterTestingModule} from '@angular/router/testing'; import {getTestScheduler} from 'jasmine-marbles'; import {MockApiTestingModule} from '../mock-api/mock-api-testing.module'; import {By} from '@angular/platform-browser'; +import {PageModule} from '../clazz/page/page.module'; describe('StudentComponent', () => { let component: StudentComponent; @@ -15,7 +16,8 @@ describe('StudentComponent', () => { declarations: [StudentComponent], imports: [ RouterTestingModule, - MockApiTestingModule + MockApiTestingModule, + PageModule ] }) .compileComponents(); @@ -31,7 +33,7 @@ describe('StudentComponent', () => { expect(component).toBeTruthy(); }); ``` 并在学生列表组件中加入分页组件: V层: ```html +++ b/first-app/src/app/student/student.component.html @@ -35,3 +35,5 @@ </tr> </tbody> </table> + +<app-page [page]="pageData" (bePageChange)="onPage($event)"></app-page> ``` C层: ```typescript +++ b/first-app/src/app/student/student.component.ts @@ -26,4 +26,9 @@ export class StudentComponent implements OnInit { onDelete(index: number, id: number): void { } + + onPage($event: number): void { + this.page = $event; + this.ngOnInit(); + } } ``` 测试分页组件成功显示,点击分页按钮生效。 ![image-20210522090115935](https://img.kancloud.cn/e1/ac/e1acb520c3475effd5d92c762184dede_1710x462.png) 最后,为了保障在集成测试中的功能,我们在学生模块中加入路由模块以及分页模块: ```typescript +++ b/first-app/src/app/student/student.module.ts @@ -3,13 +3,17 @@ import {CommonModule} from '@angular/common'; import {AddComponent} from './add/add.component'; import {ReactiveFormsModule} from '@angular/forms'; import { StudentComponent } from './student.component'; +import {PageModule} from '../clazz/page/page.module'; +import {RouterModule} from '@angular/router'; @NgModule({ declarations: [AddComponent, StudentComponent], imports: [ CommonModule, - ReactiveFormsModule + ReactiveFormsModule, + PageModule, + RouterModule ] }) export class StudentModule { ``` ### 重构 重构是保障项目一直优秀的有效手段,也是必经之路。当前在`onPage`方法中调用了`ngOnInit`方法。在功能实现上这并没有任何问题,在却与**规则**相冲突。我们在讲`Angular`生命周期时提出`ngOnInit`方法将在组件初始化完成后执行1次,且只执行1次。但如果在`onPage`方法中调用`ngOnInit`方法,则会使得某点击一次分页`onOnInit`方法都会执行一次,这明显与其`只执行1次`的规则相违背,在开发中应该规避这种不规范的行为: ```typescript +++ b/first-app/src/app/student/student.component.ts export class StudentComponent implements OnInit { pageData = {} as Page<Student>; page = 0; size = environment.size; constructor(private studentService: StudentService) { } ngOnInit(): void { this.loadData(this.page); } loadData(page: number): void { this.studentService.pageOfCurrentTeacher({ page, size: this.size }).subscribe(data => this.pageData = data); } onDelete(index: number, id: number): void { } onPage($event: number): void { this.page = $event; this.loadData(this.page); } } ``` 如此以来,我们既没有造重复的轮子,也没有违背`ngOnInit`只会被执行一次的规则。最后为自定义的方法加入注释: ```typescript +++ b/first-app/src/app/student/student.component.ts @@ -21,6 +21,10 @@ export class StudentComponent implements OnInit { this.loadData(this.page); } + /** + * 加载数据 + * @param page 第几页 + */ loadData(page: number): void { this.studentService.pageOfCurrentTeacher({ page, @@ -31,6 +35,10 @@ export class StudentComponent implements OnInit { onDelete(index: number, id: number): void { } + /** + * 点击分页时触发 + * @param $event 第几页 + */ onPage($event: number): void { this.page = $event; this.loadData(this.page); ``` ## 总结 angular模块化的概念可以最大程度的满足我们对功能的复用。本节通过新建模块以及利用`export`的特性将分页功能由原班级模块中分离,使得学生模块与班级模块共用一个分页组件,这为日后的分页组件重构提供了坚实的基础,是不造重复轮子的有效方法。 | 名称 | 链接 | | -------- | ------------------------------------------------------------ | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step7.4.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.4.3.zip) |