Angular 雖然內建許多的指令 @Directive
與管道 @Pipe
,但專案開始進行後,就難免遇到需要自訂一些服務的時候,針對一些可以共用的方法或畫面,上一篇是子元件的範例 @Component
,本篇則延伸需求,自訂 @Directive
與管道 @Pipe
來滿足 my-app 的應用。
經測試人員回饋在租戶管理介面新增或編輯租戶的時候,如果按下確定按鈕時,不小心手抖造成連續點擊的話,將導致新增租戶 API 被連續調用兩次。
解決方式,製作 DebounceClick
指令,適用於各畫面的按鈕或任何可被點擊的地方,用來防止使用者不小心手抖,而造成的連續點擊問題。
src\app\shared\directives
ng g d debounce-click
src\app\shared\directives\debounce-click.directive.ts
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Directive({
selector: '[appDebounceClick]',
})
export class DebounceClickDirective {
constructor() {}
/**
* ### debounceTime - 預設延遲時間
*
* @memberof DebounceClickDirective
*/
@Input() debounceTime = 1e3;
/**
* ### debounceClick - 點擊事件
*
* @memberof DebounceClickDirective
*/
@Output() debounceClick = new EventEmitter();
/**
* ### Clicks Subject - 點擊事件觀察者
*
* @private
* @memberof DebounceClickDirective
*/
private clicks = new Subject();
/**
* subscription - 訂閱點擊事件
*
* @private
* @type {Subscription}
* @memberof DebounceClickDirective
*/
private subscription: Subscription;
/**
* HostListener clickEvent - 監聽頁面點擊事件
*
* @param {*} $event
* @memberof DebounceClickDirective
*/
@HostListener('click', ['$event'])
clickEvent($event: any) {
$event.preventDefault();
$event.stopPropagation();
this.clicks.next($event);
document.body.click();
}
ngOnInit() {
this.subscription = this.clicks
.pipe(debounceTime(this.debounceTime))
.subscribe((e) => this.debounceClick.emit(e));
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
src\app\tenant\tenant.component.html
...
<nb-card-footer>
<div>
<!-- 回傳 `debounceClick` 事件,`debounceTime` 改變延遲時間(預設 1 秒) -->
<button
nbButton
status="primary"
appDebounceClick
(debounceClick)="ref.close('ok')"
[debounceTime]="700"
>
確定
</button>
<button nbButton (click)="ref.close()">取消</button>
</div>
...
</nb-card-footer>
...
順利解決使用者手抖問題。
經測試人員回報在土地號碼列表 總筆數元件所呈現的數字,沒有轉千分位顯示,數字較難閱讀。
解決方式,製作 SeparatorPipe
管道,可以自訂 Pipe 做一個共用管道,當數值丟進來時去做轉換顯示。
src\app\shared\directives
ng g p separator
src\app\shared\pipes\separator.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
/**
* ## 共用管道 - separator
*
* @export
* @class SeparatorPipe
* @implements {PipeTransform}
*/
@Pipe({
name: 'separator',
})
export class SeparatorPipe implements PipeTransform {
// 只要超過 3 個字串,也就是 1000 的時候,就會自行在千位數後面加上逗號
transform(value: number | string): string {
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
}
src\app\land-record\land-record.component.html
<div class="land-record">
...
<span>
<!-- 只要超過 3 個字串,也就是 1000 的時候,就會自行在千位數後面加上逗號 -->
{{ landRecords | separator }}
</span>
</div>
滿足轉千分位的需求。
src\app\shared
src\app\shared\components\input-data
src\app\shared\directives\debounce-click.directive.ts
src\app\shared\pipes\separator.pipe.ts
src\app\shared\shared.define.ts
// 共用元件
import { InputDataComponent } from './components';
// 共用指令
import { DebounceClickDirective } from './directives/debounce-click.directive';
// 共用管道
import { SeparatorPipe } from './pipes/separator.pipe';
export const COMPONENTS = [InputDataComponent, DebounceClickDirective, SeparatorPipe];
src\app\shared\shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { COMPONENTS } from './shared.define';
import { NEBULAR_ALL } from '@define/nebular/nebular.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [...COMPONENTS],
imports: [CommonModule, FormsModule, ReactiveFormsModule, ...NEBULAR_ALL],
exports: [...COMPONENTS],
entryComponents: [...COMPONENTS],
})
export class SharedModule {}
SharedModule
引用到 根模組src\app\app.module.ts
// Shared
import { SharedModule } from '@shared/shared.module';
@NgModule({
declarations: [AppComponent, HomeComponent, TenantComponent, AboutComponent, LandComponent, LandRecordComponent],
imports: [
...
SharedModule
]
...
})
export class AppModule {}
引用到其他功能模組的方式也一樣。
善用 Angular 的元件化結構,將可以共用的元件分別抽取出來,集中管理於 shared模組,方便專案中其他功能模組引用,最重要的是提高專案整體的可讀性,建議當確定某個畫面或功能會被重複使用的時候,就找個時間重構一下,讓專案更具可維護性。
隨著市場需求變化,專案需要加入多國語系來讓更多使用者能順利操作,下一篇跟大家分享如何在 my-app 使用 i18n。
Angular Debounce Click Directive