iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0
Modern Web

angular專案開發指南系列 第 21

共用元件的動態載入(1)

  • 分享至 

  • xImage
  •  

前言

租賃代管業務中除了租戶管理,租賃物件本身宣傳廣告也是很重要的需求,為了吸引顧客上門,客戶要求提供一個元件給他們維護物件的資訊,廣告格式有,

  1. 文字敘述 - Text 格式
  2. 頁面連結 - Link 格式
  3. Webview - Html 格式

迫於時程壓力,先上這三個功能,後續再增加影音格式吧。


建立 PublicityComponent 元件

建立元件的方式請參考前幾篇,ng g c publicity 得到 PublicityComponent,然後實現 廣告編輯區結果預覽區

g12

在 [PublicityComponent] 元件中

src\app\publicity\publicity.component.ts

...

export class PublicityComponent implements OnInit {
    ...

    editorType = 'text';

    editorContent = '';

    showResult = false;

    editorTypeChange(type = 'text') {
        this.showResult = false;
        this.editorType = type;
    }

    previewContent($event: any) {
        this.editorContent = $event.target.value;
    }

    previewEditor() {
        this.showResult = true;
    }

    ...
}

src\app\publicity\publicity.component.html

<div class="publicity">
    <!-- 廣告編輯器 -->
    <nb-card class="editor">
        <h4>廣告編輯格式</h4>
        <div class="editor-header">
            <button nbButton status="info" (click)="editorTypeChange('text')">廣告文字</button>
            <button nbButton status="success" (click)="editorTypeChange('link')">廣告連結</button>
            <button nbButton status="warning" (click)="editorTypeChange('html')">廣告 HTML</button>
        </div>
        <div class="editor-body">
            <!-- 廣告文字編輯元件 -->
            <div class="qa-block" *ngIf="editorType === 'text'">
                <div class="title">廣告文字編輯元件</div>
                ...
            </div>

            <!-- 廣告連結編輯元件 -->
            <div class="qa-block" *ngIf="editorType === 'link'">
                <div class="title">廣告連結編輯元件</div>
                ...
            </div>

            <!-- 廣告 HTML 編輯元件 -->
            <div class="qa-block" *ngIf="editorType === 'html'">
                <div class="title">廣告 HTML 編輯元件</div>
                ...
            </div>
        </div>
        <div class="editor-footer">
            <button nbButton status="primary" (click)="previewEditor()">預覽</button>
        </div>
    </nb-card>

    <!-- 結果預覽 -->
    <nb-card class="previewer" *ngIf="showResult">
        <div class="header">結果預覽</div>
        ...
    </nb-card>
</div>

需求快速的完成了,在畫面上我們簡單的使用 結構型指令 *ngIf 來做三種廣告格式的類型判斷,當格式種類少的時候可以這樣實作,一旦廣告格式多了之後,就必須在 Html 上大量的應用 *ngIf,代碼也會變得難以維護,所以打算重構,使用載入元件的方式將廣告格式透過 *ngComponentOutlet 的方式來動態載入。


重構 PublicityComponent 元件

使用 *ngComponentOutlet 重構 [PublicityComponent] 元件

src\app\publicity\publicity.component.html

<div class="publicity">
    <!-- 廣告編輯器 -->
    <nb-card class="editor">
        <h4>廣告編輯格式</h4>
        ...
        <div class="editor-body">
            <!-- *ngComponentOutlet 動態載入 -->
            <ng-template *ngComponentOutlet="componentList.get(editorType)"></ng-template>
        </div>
        ...
    </nb-card>

    <!-- 結果預覽 -->
    <nb-card class="previewer" *ngIf="showResult">
        ...
    </nb-card>
</div>

三種廣告格式分別重構為元件 (TextComponent, LinkComponent, HtmlComponent)

src\app\publicity\publicity.component.ts

@Component({
    selector: 'app-publicity-text',
    template: `
        <div class="qa-block">
            <div class="title">廣告文字編輯元件</div>
            <textarea
                #textarea
                nbInput
                fullWidth
                class="text-area"
                rows="5"
                (input)="previewContent($event)"
                style="width:100%"
            ></textarea>
        </div>
    `,
    styleUrls: ['./publicity.component.scss'],
})
...

將廣告格式元件整理到 SharedModule

[TextComponent] src\app\shared\components\text
[LinkComponent] src\app\shared\components\link
[HtmlComponent] src\app\shared\components\html

載入廣告格式元件

src\app\publicity\publicity.component.ts

import { Component, OnInit } from '@angular/core';
import { TextComponent, LinkComponent, HtmlComponent } from '@shared/components';

@Component({
    selector: 'app-publicity',
    templateUrl: './publicity.component.html',
    styleUrls: ['./publicity.component.scss'],
})
export class PublicityComponent implements OnInit {
    ...

    componentList = new Map<string, any>([
        ['text', TextComponent],
        ['link', LinkComponent],
        ['html', HtmlComponent],
    ]);

    ...
}

g13


元件之間的資料傳遞 - Rxjs

雖然可以自由切換三種廣告格式元件,但是各元件寫入的資料好像無法反應到 PublicityComponent 元件了,以下方法讓資料在各元件之間傳遞起來。

建一個 Rxjs Subject Observable 統一紀錄廣告格式填寫的內容

src\app\core\state\publicity\publicity.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
    providedIn: 'root',
})
export class PublicityService {
    constructor() {}

    /**
     * ### Rxjs publicityState
     *
     * @memberof PublicityService
     */
    public publicityState = new Subject<any>();
    publicityState$ = this.publicityState.asObservable();

    /**
     * ### 設定廣告內容
     *
     * @param {*} value
     * @memberof PublicityService
     */
    setState(value: any): void {
        this.publicityState.next(value);
    }
}

[TextComponent] 將輸入內容寫到 publicityState

src\app\shared\components\text\text.component.ts

import { Component, OnInit } from '@angular/core';
import { PublicityService } from '@core/state';

@Component({
    selector: 'app-text',
    templateUrl: './text.component.html',
    styleUrls: ['./text.component.scss'],
})
export class TextComponent implements OnInit {
    constructor(private publicityService: PublicityService) {}

    /**
     * ### 各元件內容寫入 Observable - publicityState
     *
     * @param {*} $event
     * @memberof TextComponent
     */
    previewContent($event: any) {
        this.publicityService.setState($event.target.value);
    }

    ngOnInit(): void {}
}

訂閱 publicityState 將結果寫回 PublicityComponent 元件

src\app\publicity\publicity.component.ts

import { Component, OnInit } from '@angular/core';
import { TextComponent, LinkComponent, HtmlComponent } from '@shared/components';
import { PublicityService } from '@core/state';

@Component({
    selector: 'app-publicity',
    templateUrl: './publicity.component.html',
    styleUrls: ['./publicity.component.scss'],
})
export class PublicityComponent implements OnInit {
    constructor(private publicityService: PublicityService) {}
    ...

    ngOnInit(): void {
        this.publicityState = this.publicityService.publicityState$.subscribe((resp) => {
            this.editorContent = resp;
        });
    }

    ngOnDestroy(): void {
        this.publicityState = null;
    }
}

成果畫面

g12

與原本結果相同,差別在於現在三種廣告格式都是動態載入的。


結論

本篇介紹如何使用動態載入元件的方式重構原本即將要越來越亂的程式碼,透過程式邏輯的控制,讓畫面長出業務邏輯對應的元件,供客戶使用。

下一篇將進一步探討動態元件載入器 Dynamic component loader 的作法。


參考

NgComponentOutlet

Entry components

ComponentFactory


上一篇
i18n 多國語系 - Ngx-Translate
下一篇
共用元件的動態載入(2)
系列文
angular專案開發指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言