
在之前的文章中,我們有提到:
service 不但可以用來處理API 請求,還有其他的用途
例如,我們這篇文章要講到的notification的實作。 【相關教學推薦:《angular教學》】
效果圖如下:

UI 這個可以後製調整
So,我們一步步來分解。
新增服務
我們在app/services中新增notification.service.ts服務檔案(請使用命令列產生),新增相關的內容:
// notification.service.ts
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
// 通知狀態的枚舉export enum NotificationStatus {
Process = "progress",
Success = "success",
Failure = "failure",
Ended = "ended"
}
@Injectable({
providedIn: 'root'
})
export class NotificationService {
private notify: Subject<NotificationStatus> = new Subject();
public messageObj: any = {
primary: '',
secondary: ''
}
// 轉換成可觀察體public getNotification(): Observable<NotificationStatus> {
return this.notify.asObservable();
}
// 進行中通知public showProcessNotification() {
this.notify.next(NotificationStatus.Process)
}
// 成功通知public showSuccessNotification() {
this.notify.next(NotificationStatus.Success)
}
// 結束通知public showEndedNotification() {
this.notify.next(NotificationStatus.Ended)
}
// 更改訊息public changePrimarySecondary(primary?: string, secondary?: string) {
this.messageObj.primary = primary;
this.messageObj.secondary = secondary
}
constructor() { }
}是不是很容易理解...
我們將notify變成可觀察物體,之後發佈各種狀態的資訊。
創建組件
我們在app/components這個存放公共組件的地方新建notification組件。所以你會得到下面的結構:
notification ├── notification.component.html // 頁面骨架├── notification.component.scss // 頁面獨有樣式├── notification.component.spec.ts // 測試檔└── notification.component.ts // javascript 檔案
我們定義notification的骨架:
<!-- notification.component.html -->
<!-- 支援手動關閉通知-->
<button (click)="closeNotification()">關閉</button>
<h1>提醒的內容: {{ message }}</h1>
<!-- 自訂重點通知訊息-->
<p>{{ primaryMessage }}</p>
<!-- 自訂次要通知資訊-->
<p>{{ secondaryMessage }}</p>接著,我們簡單修飾下骨架,加入下面的樣式:
// notification.component.scss
:host {
position: fixed;
top: -100%;
right: 20px;
background-color: #999;
border: 1px solid #333;
border-radius: 10px;
width: 400px;
height: 180px;
padding: 10px;
// 注意這裡的active 的內容,在通知出現的時候才有&.active {
top: 10px;
}
&.success {}
&.progress {}
&.failure {}
&.ended {}
} success, progress, failure, ended這四個類別名稱對應notification service 定義的枚舉,可以依照自己的喜好加入相關的樣式。
最後,我們加入行為javascript程式碼。
// notification.component.ts
import { Component, OnInit, HostBinding, OnDestroy } from '@angular/core';
// 新的知識點rxjs
import { Subscription } from 'rxjs';
import {debounceTime} from 'rxjs/operators';
// 引入相關的服務import { NotificationStatus, NotificationService } 從 'src/app/services/notification.service';
@Component({
selector: 'app-notification',
templateUrl: './notification.component.html',
styleUrls: ['./notification.component.scss']
})
export class NotificationComponent implements OnInit, OnDestroy {
// 防手震時間,唯讀private readonly NOTIFICATION_DEBOUNCE_TIME_MS = 200;
protected notificationSubscription!: Subscription;
private timer: any = null;
public message: string = ''
// notification service 枚舉訊息的對應private reflectObj: any = {
progress: "進行中",
success: "成功",
failure: "失敗",
ended: "結束"
}
@HostBinding('class') notificationCssClass = '';
public primaryMessage!: string;
public secondaryMessage!: string;
constructor(
private notificationService: NotificationService
) { }
ngOnInit(): void {
this.init()
}
public init() {
// 新增相關的訂閱資訊this.notificationSubscription = this.notificationService.getNotification()
.pipe(
debounceTime(this.NOTIFICATION_DEBOUNCE_TIME_MS)
)
.subscribe((notificationStatus: NotificationStatus) => {
if(notificationStatus) {
this.resetTimeout();
// 新增相關的樣式this.notificationCssClass = `active ${ notificationStatus }`
this.message = this.reflectObj[notificationStatus]
// 取得自訂首要資訊this.primaryMessage = this.notificationService.messageObj.primary;
// 取得自訂次要資訊this.secondaryMessage = this.notificationService.messageObj.secondary;
if(notificationStatus === NotificationStatus.Process) {
this.resetTimeout()
this.timer = setTimeout(() => {
this.resetView()
}, 1000)
} else {
this.resetTimeout();
this.timer = setTimeout(() => {
this.notificationCssClass = ''
this.resetView()
}, 2000)
}
}
})
}
private resetView(): void {
this.message = ''
}
// 關閉定時器private resetTimeout(): void {
if(this.timer) {
clearTimeout(this.timer)
}
}
// 關閉通知public closeNotification() {
this.notificationCssClass = ''
this.resetTimeout()
}
// 元件銷毀ngOnDestroy(): void {
this.resetTimeout();
// 取消所有的訂閱訊息this.notificationSubscription.unsubscribe()
}
}在這裡,我們引入了rxjs這個知識點, RxJS是使用Observables的響應式程式設計的函式庫,它使編寫非同步或基於回呼的程式碼更容易。這是一個很棒的函式庫,接下來很多的文章你會接觸到它更多的內容。
這裡我們使用了debounce防手震函數,函數防手震,就是指觸發事件後,在n 秒後只能執行一次,如果在n 秒內又觸發了事件,則會重新計算函數的執行時間。簡單來說:當一個動作連續觸發,只執行最後一次。
ps:
throttle節流函數:限制一個函數在一定時間內只能執行一次。
在面試的時候,面試官很喜歡問...
呼叫
因為這個一個全域的服務,我們在app.component.html中呼叫此元件:
// app.component.html <router-outlet></router-outlet> <app-notification></app-notification>
為了方便演示,我們在user-list.component.html中新增按鈕,方便觸發演示:
// user-list.component.html <button (click)="showNotification()">click show notification</button>
觸發相關的程式碼:
// user-list.component.ts
import { NotificationService } 從 'src/app/services/notification.service';
// ...
constructor(
private notificationService: NotificationService
) { }
// 展示通知showNotification(): void {
this.notificationService.changePrimarySecondary('主要資訊1');
this.notificationService.showProcessNotification();
setTimeout(() => {
this.notificationService.changePrimarySecondary('主要資訊2', '次要資訊2');
this.notificationService.showSuccessNotification();
}, 1000)
}至此,大功告成,我們成功模擬了notification的功能。相關的服務元件我們可以依照實際的需求進行修改,滿足業務需求自訂。如果我們是開發內部使用的系統的話,建議使用成熟的UI 函式庫,它們已經幫我們封裝好各種元件和服務,大量節省我們的開發時間。