# 注銷
本節(jié)我們開始完成熟悉的注銷功能。
## 注銷1.0
實(shí)現(xiàn)注銷功能前,我們先復(fù)習(xí)下用戶登錄的步驟:
1. 在login組件上增加一個(gè)可以向外彈射事件的beLogin方法。
2. 在index組件的V層中,引入login組件,并關(guān)聯(lián)beLogin方法至index組件的onLogin方法上。
3. 用戶使用正確的用戶名、密碼登錄后,login組件將登錄成功的事件向上彈出。
4. index組件的onLogin方法接收到了彈出的事件,設(shè)置自己的login屬性為true,進(jìn)而完成了登錄功能。
那么注銷功能完全可以參考上述登錄功能完成:
1. 在nav組件上增加一個(gè)可以向外彈射事件的beLogout方法。
2. 在index組件的V層中,引入nav組件,并關(guān)聯(lián)beLogout方法至index組件的onLogout方法上。
3. 用戶點(diǎn)擊注銷按鈕后,nav組件將注銷成功的事件向上彈出。
4. index組件的onLogout方法接收到了彈出的事件,設(shè)置自己的login屬性為false,進(jìn)而完成了注銷功能。
思想有了,編碼便成為了最簡單的事情:
### beLogout
來到導(dǎo)航組件,新增一個(gè)用于發(fā)送數(shù)據(jù)的`beLogout`,再增加一個(gè)用于鏈接V層的`onSubmit`:
```typescript
+import {Component, EventEmitter, OnInit, Output} from '@angular/core';
@Component({
selector: 'app-nav',
@@ -7,10 +7,16 @@ import {Component, OnInit} from '@angular/core';
})
export class NavComponent implements OnInit {
+ @Output()
+ beLogout = new EventEmitter<void>(); ??
+
constructor() {
}
ngOnInit(): void {
}
+ onSubmit(): void {
+ this.beLogout.emit(undefined); ??
+ }
}
```
當(dāng)泛型被聲明為void時(shí),可以將數(shù)據(jù)設(shè)置為`undefined`。當(dāng)然了,在大多數(shù)時(shí)候,留空也是可以了,比如上述代碼完全可以重寫為:`this.beLogout.emit();`,趕快試試吧。
V層綁定相關(guān)方法:
```html
+++ b/first-app/src/app/nav/nav.component.html
@@ -24,7 +24,7 @@
<a class="nav-link" routerLink="personal-center">個(gè)人中心</a>
</li>
</ul>
- <form class="form-inline my-2 my-lg-0">
+ <form class="form-inline my-2 my-lg-0" (ngSubmit)="onSubmit()">
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">注銷</button>
</form>
</div>
```
最后我們使用單元測試來保證上述功能的正確性,即:用代碼來測試代碼。在團(tuán)隊(duì)開發(fā)中,這還可以起到保護(hù)我們當(dāng)前代碼功能的作用,當(dāng)其它人(也極有可能是日后的自己)在開發(fā)其它功能時(shí),單元測試通過說明當(dāng)前的功能未被破壞。這在保證項(xiàng)目質(zhì)量是非常有幫助的。
在進(jìn)行單元測試,我們應(yīng)該盡量的細(xì)化測試的粒度,比如把我們剛剛的功能分為兩個(gè)測試點(diǎn):測試V層點(diǎn)擊注銷按鈕后,C層相應(yīng)的方法是否被觸發(fā);測試C層onSubmit是否按我們的想法調(diào)用了beLogout的emit方法。
```typescript
+++ b/first-app/src/app/nav/nav.component.spec.ts
@@ -22,4 +22,10 @@ describe('NavComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
+
+ fit('v層注銷按鈕', () => {
+ // 獲取V層的注銷按鈕
+ // 在c層的相關(guān)方法中安插間諜
+ // 點(diǎn)擊注銷按鈕,則間諜方法應(yīng)該被調(diào)用
+ });
});
```
正式動(dòng)手前寫寫注釋是個(gè)值得表揚(yáng)的好習(xí)慣!接下來相信你已經(jīng)有能力來完成該單元測試了。
```typescript
+++ b/first-app/src/app/nav/nav.component.spec.ts
@@ -29,4 +29,10 @@ describe('NavComponent', () => {
// 點(diǎn)擊注銷按鈕,則間諜方法應(yīng)該被調(diào)用
});
+ fit('onSubmit', () => {
+ // 接收組件的beLogout發(fā)送數(shù)據(jù)的數(shù)據(jù)
+ // 調(diào)用onSubmit方法
+ // 如果的確在1步接收成功,就說明onSubmit方法成功的彈出了數(shù)據(jù);否則,說明未成功,報(bào)異常
+
+ });
+
});
```
對`beLogout.emit`有那么點(diǎn)點(diǎn)特殊。我們在前面學(xué)習(xí)過`EventEmitter`是可以按自己的意愿向上彈射數(shù)據(jù),該數(shù)據(jù)可以由父組件綁定相應(yīng)的方法的方式接收到。而在單元測試中如何使用代碼的方式來接收呢?使用代碼接收同樣也很簡單,而且我們早早的就接觸到了它們:
```typescript
+++ b/first-app/src/app/nav/nav.component.spec.ts
@@ -31,7 +31,13 @@ describe('NavComponent', () => {
fit('onSubmit', () => {
// 接收組件的beLogout發(fā)送數(shù)據(jù)的數(shù)據(jù)
+ component.beLogout.subscribe(() => {
+ console.log('接收到了數(shù)據(jù)');
+ });
+
// 調(diào)用onSubmit方法
+ component.onSubmit();
+
// 如果的確在1步接收成功,就說明onSubmit方法成功的彈出了數(shù)據(jù);否則,說明未成功,
報(bào)異常
});
```
沒錯(cuò)由于`EventEmitter`有按自己的意愿發(fā)送數(shù)據(jù)的特性,所以我們同樣可以使用`subscribe`對其進(jìn)行訂閱(關(guān)注),此時(shí)一旦`EventEmitter`有新的動(dòng)態(tài)`subscribe`中的函數(shù)則會(huì)被自動(dòng)執(zhí)行一次。

如果我們在此多執(zhí)行幾次`component.onSubmit();`,則會(huì)在控制臺中多顯示幾次`接收到了數(shù)據(jù)`,請?jiān)囋嚳础?
控制臺中同時(shí)還顯示了一個(gè)異常,該異常提示說:不能在li上綁定`routerLinkActiveOptions`屬性,因?yàn)閍ngular不認(rèn)識它。作用路由一部分的`routerLinkActiveOptions`存在于路由模塊中,所以解決該錯(cuò)誤的方法是在當(dāng)前測試文件中引入路由(測試)模塊:
```typescript
+++ b/first-app/src/app/nav/nav.component.spec.ts
@@ -1,6 +1,7 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {NavComponent} from './nav.component';
+import {RouterTestingModule} from '@angular/router/testing';
describe('NavComponent', () => {
let component: NavComponent;
@@ -8,7 +9,8 @@ describe('NavComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [NavComponent]
+ declarations: [NavComponent],
+ imports: [RouterTestingModule]
})
.compileComponents();
});
```
再次運(yùn)行單元測試,錯(cuò)誤消失:

繼續(xù)回到對onSubmit方法的測試中,我們說使用`ng t`來測試代碼成功與否是不應(yīng)該來查看控制臺確認(rèn)的,那么如何來使用代碼來保證`onSubmit方法成功的彈出了數(shù)據(jù)`呢?我們需要以下的小技巧:
```typescript
+++ b/first-app/src/app/nav/nav.component.spec.ts
@@ -33,14 +33,17 @@ describe('NavComponent', () => {
fit('onSubmit', () => {
// 接收組件的beLogout發(fā)送數(shù)據(jù)的數(shù)據(jù)
+ let called = false;
component.beLogout.subscribe(() => {
console.log('接收到了數(shù)據(jù)');
+ called = true;
});
// 調(diào)用onSubmit方法
component.onSubmit();
// 如果的確在1步接收成功,就說明onSubmit方法成功的彈出了數(shù)據(jù);否則,說明未成功,
報(bào)異常
+ expect(called).toBeTrue();
});
});
```
如此以來,如果`subscribe`方法成功的接收到了向上彈出的空數(shù)據(jù),則called變量必為true;反之如果called變量為true,則也能夠說明`subscribe`方法成功的接收到了空數(shù)據(jù)。
### onLogout
完成了注銷組件的彈出方法后,接下來在`index`組件來對接這個(gè)注銷事件:
```typescript
+++ b/first-app/src/app/index/index.component.ts
@@ -26,4 +26,10 @@ export class IndexComponent implements OnInit {
// 將登錄狀態(tài)寫入緩存
window.sessionStorage.setItem('login', 'true');
}
+
+ onLogout(): void {
+ console.log('接收到注銷組件的數(shù)據(jù)彈射,開始注銷');
+ this.login = false;
+ window.sessionStorage.removeItem('login');
+ }
}
```
接著在V層中綁定注銷組件的`(beLogout)`方法:
```html
+++ b/first-app/src/app/index/index.component.html
@@ -1,5 +1,5 @@
<!--登錄成功后,在上面顯示導(dǎo)航-->
-<app-nav *ngIf="login"></app-nav>
+<app-nav *ngIf="login" (beLogout)="onLogout()"></app-nav>
<!--在下方顯示路由對應(yīng)的具體組件-->
<router-outlet *ngIf="login"></router-outlet>
```
我們再次借助單元測試來驗(yàn)證上述代碼的正確與否,本著測試粒度最小化的原則,我們并不需要由nav組件的點(diǎn)擊注銷按鈕開始測試,而僅僅需要測試:點(diǎn)nav組件向外彈射數(shù)據(jù)時(shí),index組件是否成功的接收了數(shù)據(jù)即可。
> 單元測試的粒度控制的確需要一些時(shí)日才能運(yùn)用自如,但幸運(yùn)的是只要我們在上面加以時(shí)日便一定能運(yùn)用自如。
```typescript
+++ b/first-app/src/app/index/index.component.spec.ts
@@ -30,4 +30,11 @@ describe('IndexComponent', () => {
expect(component).toBeTruthy();
fixture.autoDetectChanges();
});
+
+ fit('與注銷組件對接', () => {
+ // 在index組件相應(yīng)的方法中安插間諜
+ // nav組件彈數(shù)據(jù)
+ // index組件接收數(shù)據(jù)
+ // 斷言間諜方法被調(diào)用,則說明nav組件彈數(shù)據(jù)后,index相應(yīng)的方法將被調(diào)用
+ });
});
```
基本的思路有了,我們像聊天一下分步補(bǔ)充功能代碼如下:
```typescript
+++ b/first-app/src/app/index/index.component.spec.ts
@@ -33,8 +33,13 @@ describe('IndexComponent', () => {
fit('與注銷組件對接', () => {
// 在index組件相應(yīng)的方法中安插間諜
+ spyOn(component, 'onLogout');
+
// nav組件彈數(shù)據(jù)
+ // 如何來獲取這個(gè)nav組件呢?
+
// index組件接收數(shù)據(jù)
// 斷言間諜方法被調(diào)用,則說明nav組件彈數(shù)據(jù)后,index相應(yīng)的方法將被調(diào)用
+ expect(component.onLogout).toHaveBeenCalled();
});
});
```
完成功能時(shí),我們發(fā)現(xiàn)**如何來獲取NAV組件**我們還未掌握。在項(xiàng)目開發(fā)中,我們應(yīng)該優(yōu)先規(guī)整整個(gè)項(xiàng)目中尚未掌握的技術(shù)點(diǎn),優(yōu)先的來解決它們,當(dāng)這個(gè)尚未掌握的點(diǎn)被解決后,一個(gè)項(xiàng)目大概什么時(shí)候能完工就能心中有點(diǎn)數(shù)了。
在單元測試中,我們可能通過放置測試組件的夾具`fixture.debugElement`來獲取到測試過程中其它的組件:
```typescript
+++ b/first-app/src/app/index/index.component.spec.ts
@@ -7,6 +7,7 @@ import {HttpClientModule} from '@angular/common/http';
import {FormsModule} from '@angular/forms';
import {RouterTestingModule} from '@angular/router/testing';
import {NavComponent} from '../nav/nav.component';
+import {By} from '@angular/platform-browser'; ??
describe('IndexComponent', () => {
let component: IndexComponent;
@@ -37,6 +38,8 @@ describe('IndexComponent', () => {
// nav組件彈數(shù)據(jù)
// 如何來獲取這個(gè)nav組件呢?
+ const navComponent = fixture.debugElement.query(By.directive(NavComponent));
+ console.log(navComponent);
// index組件接收數(shù)據(jù)
// 斷言間諜方法被調(diào)用,則說明nav組件彈數(shù)據(jù)后,index相應(yīng)的方法將被調(diào)用
```
**注意**:整個(gè)項(xiàng)目中有好幾個(gè)`By`,這里需要使用 ?? 所指的這個(gè)。
運(yùn)行單元測試,卻好像**意外**的報(bào)錯(cuò)了:

之所以說好像,是由于我們犯了**想當(dāng)然、我認(rèn)為、應(yīng)該**的錯(cuò)誤,只所以沒有獲取到nav組件,并不是由于我們的代碼出現(xiàn)什么邏輯性的、關(guān)鍵的錯(cuò)誤,而是當(dāng)前的單元測試中的確就不存在nav組件:

向下滾動(dòng)單元測試便可以輕易發(fā)現(xiàn)當(dāng)然是用戶未登錄狀態(tài),所以顯示了登錄組件,而nav組件由于未使用到,所以angular并沒有實(shí)例化它(在用到的時(shí)候才實(shí)例化,這是節(jié)約資源的一種有效手段),那么此時(shí)獲取不到nav組件當(dāng)然是正常的。
那么是否需要使用模擬登錄的方法來顯示出nav組件呢?答案是否定的。因?yàn)槲覀兺耆槐剡@么做。在當(dāng)前組件中,是否顯示nav組件,取決于當(dāng)前index組件中的login屬性,所以預(yù)顯示nav組件,僅僅將login屬性的值設(shè)置為true便可以實(shí)現(xiàn)。
```typescript
+++ b/first-app/src/app/index/index.component.spec.ts
@@ -33,6 +33,10 @@ describe('IndexComponent', () => {
});
fit('與注銷組件對接', () => {
+ // 顯示民航組件
+ component.login = true;
+ fixture.detectChanges(); ??
+
// 在index組件相應(yīng)的方法中安插間諜
spyOn(component, 'onLogout');
```
C層的屬性變更后,必須通知測試夾具(fixture)重新渲染V層,否則V層將保持原樣。 ??

最后我們在獲取到的nav組上發(fā)送數(shù)據(jù):
```typescript
+++ b/first-app/src/app/index/index.component.spec.ts
@@ -44,6 +44,8 @@ describe('IndexComponent', () => {
// 如何來獲取這個(gè)nav組件呢?
const navComponent = fixture.debugElement.query(By.directive(NavComponent));
console.log('獲取到了導(dǎo)航組件', navComponent);
+ const navComponentInstance = navComponent.componentInstance as NavComponent; ??
+ navComponentInstance.beLogout.emit();
// index組件接收數(shù)據(jù)
// 斷言間諜方法被調(diào)用,則說明nav組件彈數(shù)據(jù)后,index相應(yīng)的方法將被調(diào)用
```
通過`navComponent.componentInstance`來獲取組件的實(shí)例,使用as關(guān)鍵字來指定一個(gè)類型。如果不使用as關(guān)鍵字指定類型則navComponentInstance變量的類型將被認(rèn)為是any,這也是可以的,但你不應(yīng)該這樣做。
單元測試通過,說明index組件成功的獲取了注銷組件彈出的**注銷**事件,至此兩個(gè)組件的對接在單元測試的支持下被完美的完成了。

最后我們啟用`ng s`來啟動(dòng)應(yīng)用,使用用戶名密碼登錄后再點(diǎn)擊注銷按鈕,最終驗(yàn)證功能的正確性,過程略。
## 后臺注銷2.0
我們剛剛看似完成了注銷功能,但實(shí)際上這是一種極不負(fù)責(zé)的方式。糾其原因則是我們犯了一個(gè)在生產(chǎn)中經(jīng)常容易犯的毛病:不按規(guī)范行事。
在上個(gè)小節(jié)中我們給出了后臺注銷的API:
```bash
GET /teacher/logout
```
但我們剛剛好像并沒有使用到,這種不規(guī)范可以被壞人非常輕松的利用,比如我們剛剛使用了公共電腦登錄本系統(tǒng):登錄、使用、注銷。然后我們放心的離開了,現(xiàn)在壞人登場。壞人打開瀏覽器的控制臺,來到Storge界面。

接下來加入如下信息:

key值寫login,value寫入true,接著刷新瀏覽器。噔噔噔噔,一個(gè)由軟小白開發(fā)的系統(tǒng)就這樣成功的被壞人利用了。
有人說那我們是否可以在注銷時(shí)把`x-auth-token`也清空,這樣用于認(rèn)證的`x-auth-token`沒有了,壞人就沒有辦法訪問一些后臺對權(quán)限認(rèn)證的資源(比如個(gè)人中心)了。沒錯(cuò),如果壞人很簡單,這種思想是沒有問題的。我們復(fù)習(xí)一下前后臺使用cookie的認(rèn)證模式:

在認(rèn)證過程中,我們使用x-auth-token替換了cookie實(shí)現(xiàn)了用戶認(rèn)證。無論是cookie還是x-auth-token,這都像極了現(xiàn)實(shí)生活中的各種**會(huì)員卡**,或是沒有密碼的**信用卡**。在實(shí)際生活的日常消費(fèi)中**信用卡**可做為消費(fèi)憑證完成用戶與銀行的認(rèn)證過程。那么我們應(yīng)該如何來注銷一張**信用卡**呢?
如果我們在注銷時(shí)再聰明的把`x-auth-token`也一并清空,則實(shí)際上相當(dāng)于我們在注銷銀行卡時(shí)沒有去銀行,而是直接把信用卡片仍入了垃圾桶。但是銀行方的信用卡信息并未消除,卡片信息仍然有效。所以如果這張被棄用的信用卡作用被壞人由垃圾桶中拾取的話,是完全可以繼續(xù)使用的。歷史上我們使用的銀行卡都是磁條式的,這種銀行卡具有高度的可復(fù)制功能,所以在磁條卡的時(shí)代發(fā)生過不少的銀行卡被盜刷的事件。而如果發(fā)現(xiàn)銀行卡被盜刷,受害人只把自己手中的銀行卡扔入垃圾桶是完全無濟(jì)于事的。
這種壞人也很容易做到,它僅需要把握好一個(gè)做案時(shí)機(jī)即可:用戶使用過程去下WC或是去吸只煙。整下過程如下:
1. 用戶登錄系統(tǒng)
2. 半路去吸煙
3. 壞人出場,去緩存中獲取這個(gè)`x-auth-token`,接著離場
4. 用戶繼續(xù)使用系統(tǒng)
5. 用戶注銷
6. 然后壞人僅需要在瀏覽器的緩存中輸入這個(gè)`x-auth-token`,同將`login`設(shè)置為`true`便可繼續(xù)的操作本系統(tǒng)(甚至是同步的)
上述**模擬犯罪**的過程請自行嘗試。相信你現(xiàn)在知道為什么我們在使用銀卡時(shí)為什么要遵循以下規(guī)則了吧:
1. 如果可以辦理芯片式的銀行卡,則不應(yīng)該辦理磁條式的。因?yàn)榇艞l式銀行卡可有可復(fù)制的特性。壞人可以使用相關(guān)的設(shè)備在瞬間復(fù)制一張具有相同信息的副卡出來。
2. 在消費(fèi)刷卡時(shí),不應(yīng)該讓銀行卡離開自己的視線,不給壞人復(fù)制的機(jī)會(huì)。
3. 現(xiàn)在大多數(shù)的POS機(jī)會(huì)制定一個(gè)規(guī)則:如果當(dāng)前的銀行卡有芯片,則必須刷芯片才能完成支付。這是對儲(chǔ)戶的一種保護(hù)。
鋪墊了這么多,一是為了使你的大腦更容易接受**按規(guī)范開發(fā)**的團(tuán)隊(duì)規(guī)范,使它由排斥、被動(dòng)接受團(tuán)隊(duì)的基本規(guī)范轉(zhuǎn)為主動(dòng)接受;二是為了以下正確的代碼做準(zhǔn)備。
```typescript
+++ b/first-app/src/app/nav/nav.component.ts
@@ -1,4 +1,5 @@
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
+import {HttpClient} from '@angular/common/http';
@Component({
selector: 'app-nav',
@@ -10,13 +11,16 @@ export class NavComponent implements OnInit {
@Output()
beLogout = new EventEmitter<void>();
- constructor() {
+ constructor(private httpClient: HttpClient) {
}
ngOnInit(): void {
}
onSubmit(): void {
- this.beLogout.emit(undefined);
+ const url = 'http://angular.api.codedemo.club:81/teacher/logout';
+ this.httpClient.get(url)
+ .subscribe(() => this.beLogout.emit(undefined),
+ error => console.log('logout error', error));
}
}
```
如此我們便完成了注銷功能:后臺的注銷、前臺的注銷。使用`ng s`進(jìn)行相應(yīng)測試,測試通過。
## 本節(jié)作業(yè)
1. 完成nav組件`v層注銷按鈕`測試用例的編寫。
2. 移除所有的`fit`,使用`ng t`來對全局進(jìn)行測試,你將發(fā)現(xiàn)一些錯(cuò)誤,請嘗試修正它們。
| 名稱 | 鏈接 | 備注 |
| ----------------------- | ------------------------------------------------------------ | ------------------------- |
| 對嵌套組件的測試 | [https://angular.cn/guide/testing-components-scenarios#nested-component-tests](https://angular.cn/guide/testing-components-scenarios#nested-component-tests) | |
| 搭建http請求測試環(huán)境 | [https://angular.cn/guide/http#setup-for-testing](https://angular.cn/guide/http#setup-for-testing) | 你需要它來幫助你完成作業(yè)2 |
| DebugElement | [https://angular.cn/guide/testing-components-basics#debugelement](https://angular.cn/guide/testing-components-basics#debugelement) | |
| query | [https://angular.cn/api/animations/query](https://angular.cn/api/animations/query) | |
| by | [https://angular.cn/api/platform-browser/By](https://angular.cn/api/platform-browser/By) | |
| 本節(jié)源碼(含作業(yè)2答案) | [https://github.com/mengyunzhi/angular11-guild/archive/step5.6.zip](https://github.com/mengyunzhi/angular11-guild/archive/step5.6.zip) | |
- 序言
- 第一章 Hello World
- 1.1 環(huán)境安裝
- 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 請求后臺數(shù)據(jù)
- 2.2.1 HttpClient
- 2.2.2 請求數(shù)據(jù)
- 2.2.3 模塊與依賴注入
- 2.2.4 異步與回調(diào)函數(shù)
- 2.2.5 集成測試
- 2.2.6 本章小節(jié)
- 2.3 新增教師
- 2.3.1 組件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 對接后臺
- 2.3.4 路由
- 2.4 編輯教師
- 2.4.1 組件初始化
- 2.4.2 獲取路由參數(shù)
- 2.4.3 插值與模板表達(dá)式
- 2.4.4 初識泛型
- 2.4.5 更新教師
- 2.4.6 測試中的路由
- 2.5 刪除教師
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome圖標(biāo)庫
- 2.6.3 firefox
- 2.7 總結(jié)
- 第三章 用戶登錄
- 3.1 初識單元測試
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 著陸組件
- 3.5 @Output
- 3.6 TypeScript 類
- 3.7 瀏覽器緩存
- 3.8 總結(jié)
- 第四章 個(gè)人中心
- 4.1 原型
- 4.2 管道
- 4.3 對接后臺
- 4.4 x-auth-token認(rèn)證
- 4.5 攔截器
- 4.6 小結(jié)
- 第五章 系統(tǒng)菜單
- 5.1 延遲及測試
- 5.2 手動(dòng)創(chuàng)建組件
- 5.3 隱藏測試信息
- 5.4 規(guī)劃路由
- 5.5 定義菜單
- 5.6 注銷
- 5.7 小結(jié)
- 第六章 班級管理
- 6.1 新增班級
- 6.1.1 組件初始化
- 6.1.2 MockApi 新建班級
- 6.1.3 ApiInterceptor
- 6.1.4 數(shù)據(jù)驗(yàn)證
- 6.1.5 教師選擇列表
- 6.1.6 MockApi 教師列表
- 6.1.7 代碼重構(gòu)
- 6.1.8 小結(jié)
- 6.2 教師列表組件
- 6.2.1 初始化
- 6.2.2 響應(yīng)式表單
- 6.2.3 getTestScheduler()
- 6.2.4 應(yīng)用組件
- 6.2.5 小結(jié)
- 6.3 班級列表
- 6.3.1 原型設(shè)計(jì)
- 6.3.2 初始化分頁
- 6.3.3 MockApi
- 6.3.4 靜態(tài)分頁
- 6.3.5 動(dòng)態(tài)分頁
- 6.3.6 @Input()
- 6.4 編輯班級
- 6.4.1 測試模塊
- 6.4.2 響應(yīng)式表單驗(yàn)證
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定義FormControl
- 6.4.6 代碼重構(gòu)
- 6.4.7 小結(jié)
- 6.5 刪除班級
- 6.6 集成測試
- 6.6.1 惰性加載
- 6.6.2 API攔截器
- 6.6.3 路由與跳轉(zhuǎn)
- 6.6.4 ngStyle
- 6.7 初識Service
- 6.7.1 catchError
- 6.7.2 單例服務(wù)
- 6.7.3 單元測試
- 6.8 小結(jié)
- 第七章 學(xué)生管理
- 7.1 班級列表組件
- 7.2 新增學(xué)生
- 7.2.1 exports
- 7.2.2 自定義驗(yàn)證器
- 7.2.3 異步驗(yàn)證器
- 7.2.4 再識DI
- 7.2.5 屬性型指令
- 7.2.6 完成功能
- 7.2.7 小結(jié)
- 7.3 單元測試進(jìn)階
- 7.4 學(xué)生列表
- 7.4.1 JSON對象與對象
- 7.4.2 單元測試
- 7.4.3 分頁模塊
- 7.4.4 子組件測試
- 7.4.5 重構(gòu)分頁
- 7.5 刪除學(xué)生
- 7.5.1 第三方dialog
- 7.5.2 批量刪除
- 7.5.3 面向?qū)ο?/a>
- 7.6 集成測試
- 7.7 編輯學(xué)生
- 7.7.1 初始化
- 7.7.2 自定義provider
- 7.7.3 更新學(xué)生
- 7.7.4 集成測試
- 7.7.5 可訂閱的路由參數(shù)
- 7.7.6 小結(jié)
- 7.8 總結(jié)
- 第八章 其它
- 8.1 打包構(gòu)建
- 8.2 發(fā)布部署
- 第九章 總結(jié)