本節我們嘗試引入一個更加友好的對話框來替換原生的`confirm`對話。
對話框在英文中又稱為`alert`、`Dialog`或是 `notifications`,以`html confirm beautiful dialog`為關鍵字搜索,可以找到很多優秀的組件。比如[https://www.cssscript.com/tag/confirm/](https://www.cssscript.com/tag/confirm/)一文中給出了30種具體這樣功能的組件:`Notiflix`,`Cute Alert`,`Asteroid Alert`,`sweet alert`,`Confirmo`等。
如此眾多的組件(包),到底該怎么選擇呢?建議如下:
1. 如果有一篇類似于總結的文章,優先選擇位于前面的包。因為雖然它可能不是最少的,但卻必然不會是最差的。
2. 優先選擇`npm`已托管的包(組件),包被`npm`托管說明該項目不會太過時,同時安裝起來也不會太難。
3. 優先使用自己說明文檔能被自己看懂的包,這樣的阻力最小。
4. 優先選擇提供`demo`的包,有`demo`參考性更強,成功集成的概率也會越大。
5. 優先使用周下載量更高的包。`npmjs`對每個包都會有個周下載量的值,該值越大說明使用該包的用戶越多,也就意味著其被更多的用戶認同。
在第三包的使用過程中,應該本著先完成后完美的思想。先把組件跑起來,然后再逐步的優化,以達到自己理想的目標。
## Notiflix
接下來我們以 Notiflix 為例,展示將其引入到當前項目中的步驟。
之所以選擇 Notiflix,是因為它在剛剛提到的文章中處于第一個,相信效果肯定不會太差。接下來來到[https://www.npmjs.com/package](https://www.npmjs.com/package),以 Notiflix 為關鍵字進行查找。

發現找到它的身影。這意味著它已被`npm`托管,所以不會太過時,安裝起來應該也不會太難。
接著點擊搜索的結果,來到 Notiflix 在 npm中的首頁 [https://www.npmjs.com/package/notiflix](https://www.npmjs.com/package/notiflix),按提示進行安裝。
### Npm i
在大多部的npm包的文件中,都會出現如下的安裝方式:
```bash
npm i xxx
```
其實這里的參數`i`是`install`的簡寫,所以上述寫法等同于`npm install xxx`。同時由于我們是團隊開發,所以需要把自己當前依賴的第三方包以`配置文件`的形式共享給其它成員,這個`配置文件`也就是項目根目錄下的`package.json`。而若想將安裝`xxx`的信息自動添加到`package.json`中,則需要使用`-s`參數。所以如果得到的安裝命令是`npm i xxx`,我們實際上執行的是:
```bash
npm i xxx -s
```
或者全稱:
```bash
npm install xxx --save
```
當然了,如果你愿意使用:`npm i xxx --save`或`npm install xxx -s`也是完成可以的。
### 安裝
按文檔的提示,安裝 notiflix 的命令為:`npm i notiflix`。我們使用shell來到項目根路徑,執行:
```bash
npm i notiflix -s
```
為與教程保持一致的效果,請使用以下命令來指定安裝版本:
```bash
npm i notiflix@3.0.1 -s
```
安裝結果如下:
```bash
panjie@panjies-iMac first-app % npm i notiflix@3.0.1 -s
+ notiflix@3.0.1
added 1 package from 1 contributor, removed 1 package and audited 1473 packages in 8.805s
84 packages are looking for funding
run `npm fund` for details
found 478 vulnerabilities (88 moderate, 389 high, 1 critical)
run `npm audit fix` to fix them, or `npm audit` for details
```
## 使用
第三方包的使用方式大體上分為兩種,第一種即我們在前面引入`bootstrap`的方式:將特定的`js`、`css`文件放到`angular.json`的特定位置上。這是一種通用的作法,適用于引入所有的第三方包。
但如果當前包支持`import`引用,則完全不需要那么麻煩,比如當前 notiflix 在文檔中便明確的說明,可以使用`import`來引入。
```typescript
+++ b/first-app/src/app/student/student.component.ts
@@ -3,6 +3,7 @@ import {Page} from '../entity/page';
import {Student} from '../entity/student';
import {StudentService} from '../service/student.service';
import {environment} from '../../environments/environment';
+import {Confirm} from 'notiflix';
@Component({
selector: 'app-student',
```
同引入`environment`的作用完全一致,引用了`Confirm`后,便可以在組件中使用`Confirm`了。隨即參考官方文檔[https://www.notiflix.com/#Confirm](https://www.notiflix.com/#Confirm)將學生列表組件的`delete`方法改寫為:
```typescript
onDelete(index: number, id: number): void {
console.log(Confirm);
Confirm.show('請確認', '該操作不可逆', '確認', '取消',
() => {
this.studentService.delete(id)
.subscribe(() => this.pageData.content.splice(index, 1));
}, () => {
console.log('cancel');
});
}
```
> 在學習教程時,可能由于notiflix官方更新的原因,導致教程的方法與官方文檔不一致。
此時再次點擊刪除按鈕,將在控制臺中打印`Confirm`:

在控制臺中查看該`Confirm`的類型是個對象,該對象中存在4個可用的方法,可以的一個`show()`方法便是我們此次調用的。

該方法被調用后,一個清新的對話框便被成功的展示在了我們面前。
有人說老師你怎么就知道`Confirm.show()`方法的各個參數都代表什么,我想說在沒有看官方文檔之前,我也不知道。另外除了可以看官方文檔以外,還可以借助IDE按往`ctrl`后點擊`show`方法,進行跳轉到`Confirm.show()`的源代碼,如果源代碼寫的好,那么也可以很輕松的獲取到各個參數的意思。

當然了,你也可以懶到啥都不看,多嘗試幾次也可以蒙出來。
### 本節作業
還等什么,趕快嘗試引用一下其它的第三方對話框庫吧。
## 總結
相較于在`angular.json`添加各種配置來引用第三方庫,這種使用`import`引入的方式的確是太棒了!
## 上節參考答案:
C層:
```typescript
+++ b/first-app/src/app/student/student.component.ts
@@ -33,6 +33,11 @@ export class StudentComponent implements OnInit {
}
onDelete(index: number, id: number): void {
+ const result = confirm('確認要刪除嗎?');
+ if (result) {
+ this.studentService.delete(id)
+ .subscribe(() => this.pageData.content.splice(index, 1));
+ }
}
```
M層:
```typescript
+++ b/first-app/src/app/service/student.service.ts
@@ -28,6 +28,15 @@ export class StudentService {
return this.httpClient.post<Student>('/student', student);
}
+ /**
+ * 刪除
+ * @param id 學生ID
+ */
+ delete(id: number): Observable<void> {
+ const url = '/student/' + id.toString();
+ return this.httpClient.delete<void>(url);
+ }
+
```
對應單元測試:
```typescript
+++ b/first-app/src/app/service/student.service.spec.ts
@@ -26,6 +26,20 @@ describe('StudentService', () => {
error => console.log('error', error));
});
+ fit('delete', () => {
+ const id = Math.floor(Math.random() * 10);
+ let called = false;
+ service.delete(id).subscribe(() => {
+ called = true;
+ });
+ // 由于HTTP請求是異步的,所以在短時間內還沒有返回數據,called值仍然為false
+ expect(called).toBeFalse();
+
+ // 數據被手動返回后,called值為true
+ getTestScheduler().flush();
+ expect(called).toBeTrue();
+ });
+
```
| 鏈接 | 名稱 |
| ------------------------------------------------------------ | -------- |
| [https://github.com/mengyunzhi/angular11-guild/archive/step7.5.1.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.5.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 發布部署
- 第九章 總結