iT邦幫忙

2022 iThome 鐵人賽

DAY 19
0
Modern Web

angular專案開發指南系列 第 19

製作共用指令與管道

  • 分享至 

  • xImage
  •  

前言

Angular 雖然內建許多的指令 @Directive 與管道 @Pipe,但專案開始進行後,就難免遇到需要自訂一些服務的時候,針對一些可以共用的方法或畫面,上一篇是子元件的範例 @Component,本篇則延伸需求,自訂 @Directive 與管道 @Pipe 來滿足 my-app 的應用。

製作防連續點擊指令 - @Directive

經測試人員回饋在租戶管理介面新增或編輯租戶的時候,如果按下確定按鈕時,不小心手抖造成連續點擊的話,將導致新增租戶 API 被連續調用兩次。

解決方式,製作 DebounceClick 指令,適用於各畫面的按鈕或任何可被點擊的地方,用來防止使用者不小心手抖,而造成的連續點擊問題。

src\app\shared\directives

ng g d debounce-click

建一個共用 Directive

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();
    }
}

appDebounceClick 使用方式

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>
        &nbsp;
        <button nbButton (click)="ref.close()">取消</button>
    </div>

    ...

</nb-card-footer>

...

g10

順利解決使用者手抖問題。


千分位轉換管道 - @Pipe

經測試人員回報在土地號碼列表 總筆數元件所呈現的數字,沒有轉千分位顯示,數字較難閱讀。

解決方式,製作 SeparatorPipe 管道,可以自訂 Pipe 做一個共用管道,當數值丟進來時去做轉換顯示

src\app\shared\directives

ng g p separator

建一個共用 Pipe

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, ',');
    }
}

separator 使用方式

src\app\land-record\land-record.component.html

<div class="land-record">
    ...

    <span>
        <!-- 只要超過 3 個字串,也就是 1000 的時候,就會自行在千位數後面加上逗號 -->
        {{ landRecords | separator }}
    </span>
</div>

p120

滿足轉千分位的需求。


共用元件模組 SharedModule

管理 Angular 專案中的共用元件,指令和管道可以採用本範例的作法。

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];

共用元件都包含到 ...COMPONENTS 不必再額外 import

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防抖指令

Angular Debounce Click Directive

Formatting a date

Angular 筆記


上一篇
Angular 組件間資料的傳遞方式
下一篇
i18n 多國語系 - Ngx-Translate
系列文
angular專案開發指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言