###### tags: `Angular`
# 🥔基於工廠模式實現的應用 (二) 應用篇
本篇將說明如何應用下列`元件`、`工廠`實現需求。

### route-menu.factory.ts
==**src/app/menu/factory/route.factory.ts**==
#### 🌱說明
- 透過`路由設定`實現`選單`及`黑白名單`的應用。
#### 🌱原始碼
```typescript=
import { Injectable } from "@angular/core";
import { Router } from '@angular/router';
import { AuthNameRoute } from "src/app/route/route.type";
import { IFactory } from '../../dynamic/factory.interface';
import { IMenu } from "../menu.interface";
import { IAuth } from './../../auth/auth.interface';
import { CaService } from './../../service/ca.service';
export interface IRouteMenuPara {}
@Injectable({
providedIn: 'root'
})
export class RouteMenuFactory<CI extends IMenu<any> = any> implements IFactory<CI, IRouteMenuPara> {
constructor(
private router: Router,
private ca: CaService
) {
}
run(component: CI) {
component.setMenuList(this.getMenuListByRoute());
component.currentMenu = component.menuList.find(menu => `/${menu.route}` === `${this.router.url}`)
component.clickMenu.subscribe(this.clickMenu)
}
checkPara(para: IRouteMenuPara): IRouteMenuPara {
throw new Error('Method not implemented.');
}
getMenuListByRoute() {
const routes = this.router.config;
const menuList = routes
.filter(r => r?.data?.displayName)
.map(r => {
return {
displayName: r.data.displayName,
route: r.path,
isHide: !this.isValidAuth(r as AuthNameRoute)
}
})
return menuList;
}
clickMenu = (menu) => {
this.router.navigate([menu.route]);
}
isValidAuth(data: AuthNameRoute) {
if (!data.data) {
console.warn('請設定權限!');
return false;
}
const country = this.ca.getCountry();
const main = this.ca.getMain();
let isValid = true;
isValid = isValid ? this.check(country, data.data.country) : isValid;
isValid = isValid ? this.check(main, data.data.main) : isValid;
return isValid;
}
check<AE>(e: AE, auth: IAuth<AE>) {
if (auth.type === 'allow') {
return auth.list.filter(x => x === e).length > 0;
} else if (auth.type === 'block') {
return auth.list.filter(x => x === e).length < 1;
}
}
}
```
#### 🌱應用說明
**app-routing.module.ts**
```typescript=
import { EAuthCountry } from './auth/auth.interface';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { Page01Component } from './page/page01/page01.component';
import { Page02Component } from './page/page02/page02.component';
import { AuthNameRoutes } from './route/route.type';
const routes: AuthNameRoutes = [
{
path: '',
redirectTo: 'page01',
pathMatch: 'full',
},
{
path: 'page01',
component: Page01Component,
data: {
country: {
type: 'block',
list: []
},
main: {
type: 'block',
list: []
},
displayName: '第一頁',
}
},
{
path: 'page02',
component: Page02Component,
data: {
country: {
type: 'allow',
list: [EAuthCountry.新北]
},
main: {
type: 'block',
list: []
},
displayName: '第二頁',
}
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
```
一般在我們產生 `routing.module` 時,系統會預設 `routes` 的型別為 `Routes`。
那由於希望大家在設定 `routes` 時可以加上統一的參數來設定權限,讓特定的工廠可以實現權限功能,所以就擴充了 `Routes` 的型別,讓 Angular 來幫我們檢查權限參數是否正確。
**scr/app/route/route.type.ts**
```typescript=
import { IAuthRoute } from "./auth-route.interface";
import { INameRoute } from "./name-route.interface";
export type NameRoutes = INameRoute[];
export type AuthRoutes = IAuthRoute[];
export type AuthNameRoute = (INameRoute & IAuthRoute);
export type AuthNameRoutes = AuthNameRoute[]
```
從上面可以看到,`AuthNameRoute` 是由 `INameRoute` 及 `IAuthRoute` 組合而成的。
* `INameRoute` 提供顯示名稱
**scr/app/route/name-route.interface.ts**
```typescript=
import { Route } from '@angular/router';
export interface INameRoute extends Route {
data?: {
displayName: string;
}
}
```
**參數說明 :**
`displayName`: 顯示名稱
* `IAuthRoute` 提供權限設定
**scr/app/route/auth-route.interface.ts**
```typescript=
import { Route } from '@angular/router';
import { EAuthCountry, EAuthMain, IAuth } from './../auth/auth.interface';
export interface IAuthRoute extends Route {
data?: {
country: IAuth<EAuthCountry>;
main: IAuth<EAuthMain>;
}
}
```
**參數說明 :**
`country`: 縣市黑白名單設定
`main`: 權限黑白名單設定
:::info
💡小補充
`IAuth<AE>` : 黑/白名單設定型別。
```typescript
export interface IAuth<AE> {
type: 'allow' | 'block';
list: AE[];
}
```
* type, 'allow': 白名單 | 'block': 黑名單
* list, 名單列表
📝小備註
預設不阻擋的寫法: `{ type: 'block', list: [] }`
:::
### api-table-factory.ts
#### 🌱說明
* 提供表單有關 API 的處理方法
#### 🌱原始碼
```typescript=
import { Injectable } from "@angular/core";
import { concat, Observable, of } from "rxjs";
import { concatMap, tap } from "rxjs/operators";
import { IFactory } from '../../dynamic/factory.interface';
import { IField } from "../table-a/table-a.component";
import { ITable } from "../table.interface";
export interface IApiTablePara {
read$: Observable<any>[];
fieldList: IField[];
}
@Injectable({
providedIn: 'root'
})
export class ApiTableFactory<CI extends ITable<IField> = any> implements IFactory<CI, IApiTablePara> {
constructor(
) {
}
run(component: CI, para?: IApiTablePara) {
if (!para?.read$?.length) { return }
this.getDataListByRead$(para.read$).subscribe(
(dataList) => {
component.fieldList = para.fieldList;
component.dataList = this.combineData(component.dataList, dataList)
}
);
}
checkPara(obj: IApiTablePara): IApiTablePara {
return obj;
}
getDataListByRead$(read$: Observable<any>[]): Observable<any> {
return concat(read$).pipe(
concatMap((d$) => d$),
)
}
combineData<D>(data: D[], concatData: D[]) {
data = data ? data : [];
return data.concat(concatData);
}
// CRUD
onCreate(component: CI, data: any): void {
// this.dataList.push(data);
component.dataList.push(data);
}
onRead(component: CI, read$: Observable<any>[]): void {
component.dataList = [];
concat(read$).pipe(
concatMap((d$) => d$),
).subscribe((data) => {
component.dataList = component.dataList.concat(data)
})
}
onUpdate(component: CI, uuid: string, data: any): void {
const dataIndex = component.dataList.findIndex(d => d.uuid === uuid);
component.dataList[dataIndex] = data;
}
onDelete(component: CI, uuid: string): void {
const dataIndex = component.dataList.findIndex(d => d.uuid === uuid);
component.dataList.splice(dataIndex, 1);
}
}
```
#### 🌱應用說明
大家在使用表單時,通常都會需要實現 CRUD 的功能,為了要減少撰寫重複的 CRUD 功能,希望可以透過 `ApiTableFactory` 提供基本的方法達到此目的。
:::warning
⭐小提醒
`Factory` 主要還是只提供`共通`、`常用`的功能,真正實現完整的需求還是需要各自在子元件上實現。
:::
**page01.component.ts**
```typescript=
import { IMenu } from './../../menu/menu.interface';
import { WebapiService } from './../../service/webapi.service';
import { ApiTableFactory } from '../../table/factory/api-table.factory';
import { TableAComponent } from './../../table/table-a/table-a.component';
import { HomeMenuFactory } from '../../menu/factory/home-menu.factory';
import { MenuAComponent, IMenuA } from './../../menu/menu-a/menu-a.component';
import { DynamicFactory } from './../../dynamic/dynamic-factory';
import { DynamicDirective } from './../../dynamic/dynamic.directive';
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
import { RouteMenuFactory } from 'src/app/menu/factory/route-menu.factory';
import { MenuBComponent } from 'src/app/menu/menu-b/menu-b.component';
import { of } from 'rxjs';
import { areAllEquivalent } from '@angular/compiler/src/output/output_ast';
import { conditionallyCreateMapObjectLiteral } from '@angular/compiler/src/render3/view/util';
@Component({
selector: 'app-page01',
templateUrl: './page01.component.html',
styleUrls: ['./page01.component.scss']
})
export class Page01Component implements OnInit, AfterViewInit {
@ViewChild('menu', { read: DynamicDirective }) menu: DynamicDirective<MenuAComponent>;
@ViewChild('table', { read: DynamicDirective }) table: DynamicDirective<TableAComponent>;
constructor(
private dynamicF: DynamicFactory,
private routeMenuF: RouteMenuFactory,
private homeMenuF: HomeMenuFactory,
private apiTableF: ApiTableFactory,
private webApiS: WebapiService,
) { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.createMenu();
this.createTable();
}
createMenu() {
this.menu.component = this.dynamicF.create(this.menu, MenuBComponent, this.routeMenuF);
}
createTable() {
const apiTablePara = {
read$: [this.webApiS.getData(), this.webApiS.getData()],
fieldList: [
{
key: 'uuid',
displayName: '序',
isHide: false,
},
{
key: 'name',
displayName: '名稱',
isHide: false,
},
{
key: 'money',
displayName: '金額',
isHide: false,
}
],
}
// this.table.component = this.dynamicF.create(this.table, TableAComponent, this.apiTableF, this.apiTableF.checkPara(apiTablePara))
this.table.component = this.dynamicF.create(this.table, TableAComponent, this.apiTableF, this.apiTableF.checkPara(apiTablePara))
}
onClickC() {
this.apiTableF.onCreate(this.table.component, {uuid: '3', name: '莫菲斯', money: '450'});
}
onClickR() {
this.apiTableF.onRead(this.table.component, [this.webApiS.getData(), this.webApiS.getData()]);
}
onClickU() {
const newData = {uuid: '4', name: '貝拉', money: '480'}
const data = this.table.component.dataList[0];
this.apiTableF.onUpdate(this.table.component, data.uuid, newData);
}
onClickD() {
const data = this.table.component.dataList[0];
this.apiTableF.onDelete(this.table.component, data.uuid);
}
}
```
**page01.component.html**
```htmlembedded=
<alle-dynamic #menu></alle-dynamic>
<button (click)="onClickC()">新增</button>
<button (click)="onClickD()">刪除</button>
<button (click)="onClickU()">修改</button>
<button (click)="onClickR()">查詢</button>
<alle-dynamic #table></alle-dynamic>
```
### 小試身手
1. 請在 page01.component.ts 使用 TableAComponent 來實作表單功能。
2. 請在 page02.component.ts 自行實現下方需求
- 產生 MenuBComponent , 並將page02 選單名稱改成「Hello World!」
- 建立四個按鈕來實現 CRUD 功能
- 產生 TableAComponent
**(示意圖) 非實際畫面**

<!--  -->