租賃代管業務中除了租戶管理,租賃物件本身宣傳廣告也是很重要的需求,為了吸引顧客上門,客戶要求提供一個元件給他們維護物件的資訊,廣告格式有,
迫於時程壓力,先上這三個功能,後續再增加影音格式吧。
建立元件的方式請參考前幾篇,ng g c publicity
得到 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
的方式來動態載入。
*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>
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'],
})
...
[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],
]);
...
}
雖然可以自由切換三種廣告格式元件,但是各元件寫入的資料好像無法反應到 PublicityComponent 元件了,以下方法讓資料在各元件之間傳遞起來。
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);
}
}
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 {}
}
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;
}
}
與原本結果相同,差別在於現在三種廣告格式都是動態載入的。
本篇介紹如何使用動態載入元件的方式重構原本即將要越來越亂的程式碼,透過程式邏輯的控制,讓畫面長出業務邏輯對應的元件,供客戶使用。
下一篇將進一步探討動態元件載入器
Dynamic component loader
的作法。