---
# System prepended metadata

title: "\U0001F954基於工廠模式實現的應用 (一) 使用篇"
tags: [Angular]

---

###### tags: `Angular`

# 🥔基於工廠模式實現的應用 (一) 使用篇



本篇將示範如何使用下列`物件`建構應用。
![](https://i.imgur.com/SIeoIug.png)


### dynamic.directive.ts
==**src/app/dynamic/dynamic.directive.ts**==

#### 🌱說明
- 共用元件容器

#### 🌱原始碼
```typescript=
import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: 'alle-dynamic'
})
export class DynamicDirective<C> {
  component: C;
  vcRef = this.viewContainerRef;
  constructor(private viewContainerRef: ViewContainerRef) {
  }
}

```

#### 🌱使用方法

**page01.component.html**
```htmlembedded=
<alle-dynamic #menu></alle-dynamic>
```
1. 將 DynamicDirective 放置在將呈現共用元件的 `HTML` 內。 
    `<alle-dynamic></alle_dynamic>`
2. 加上 `#` 讓 `Component` 可以識別該元件。
    `#menu`

**page01.component.ts**
```typescript=
import { Component, ViewChild } from '@angular/core';
import { DynamicDirective } from './../../dynamic/dynamic.directive';
import { MenuAComponent } from './../../menu/menu-a/menu-a.component';

@Component({
  selector: 'app-page01',
  templateUrl: './page01.component.html',
  styleUrls: ['./page01.component.scss']
})
export class Page01Component {
     @ViewChild('menu', { read: DynamicDirective }) menu: DynamicDirective<MenuAComponent>;
}
```

3. 在 `Component` 內使用 `@ViewChild()` 來識別動態元件。
    `@ViewChild('menu', { read: DynamicDirective }) menu`
4. 為了能正確取得動態元件型別，要在 `@ViewChild()` 宣告 DynacmicDirective 型別，並泛型動態元件型別。
    `@ViewChild('menu', { read: DynamicDirective }) menu: DynamicDirective<MenuAComponent>;`




### dynamic.factory.ts
==**src/app/dynamic/dynamic.factory.ts**==

#### 🌱說明
- 載入共用元件

#### 🌱原始碼
```typescript=
import { ComponentFactoryResolver, Injectable, Injector, Type } from "@angular/core";
import { DynamicDirective } from "./dynamic.directive";
import { IFactory } from './factory.interface';

@Injectable({
    providedIn: 'root'
})
export class DynamicFactory {

    constructor(
        private _cfr: ComponentFactoryResolver,
        private _injector: Injector) {
    }

    create<C, Cp>(dynamicD: DynamicDirective<C>, dynamicC: Type<C>, factory?: IFactory<C, Cp>, factoryPara?: Cp): C {
        const comFactory = this._cfr.resolveComponentFactory(dynamicC);
        const comVCRef = dynamicD.vcRef;
        comVCRef.clear();
        
        const comRef = comVCRef.createComponent(comFactory, null, this._injector);
        if (factory) {
            factory.run(comRef.instance, factoryPara);
        }
        comRef.changeDetectorRef.detectChanges();
        
        return comRef.instance;
    }
}
```

#### 🌱使用方法
**page01.component.ts**
```typescript=
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { DynamicDirective } from './../../dynamic/dynamic.directive';
import { DynamicFactory } from './../../dynamic/dynamic-factory';
import { MenuAComponent } from './../../menu/menu-a/menu-a.component';

@Component({
  selector: 'app-page01',
  templateUrl: './page01.component.html',
  styleUrls: ['./page01.component.scss']
})
export class Page01Component implements AfterViewInit {
    @ViewChild('menu', { read: DynamicDirective }) menu: DynamicDirective<MenuAComponent>;
    
    constructor(
        private dynamicF: DynamicFactory,
    ) { }
    
    ngAfterViewInit(): void {
        this.menu.component = this.dynamicF.create(this.menu, MenuAComponent);
    }
}
```
1. 注入依賴 DynamicFactory 。
    `private dynamicF: DynamicFactroy`
2. 使用 DynamicFactory 來產生共用元件，這裡必須要放上`共用元件容器的參數`及`共用元件的類別`。
    `this.dynacmicF.create(this.menu, MenuAComponent)`

:::warning
在這邊要注意使用 DynamicFactory 來產生元件的時機，必須要在 ngAfterViewInit 後。
:::


### component.factory.ts
==**src/app/menu/factory/route-menu.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;
        }
    }
}
```

#### 🌱使用方法
**page01.component.ts**
```typescript=
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { DynamicDirective } from './../../dynamic/dynamic.directive';
import { DynamicFactory } from './../../dynamic/dynamic-factory';
import { MenuAComponent } from './../../menu/menu-a/menu-a.component';
import { RouteMenuFactory } from 'src/app/menu/factory/route-menu.factory';

@Component({
  selector: 'app-page01',
  templateUrl: './page01.component.html',
  styleUrls: ['./page01.component.scss']
})
export class Page01Component implements AfterViewInit {
    @ViewChild('menu', { read: DynamicDirective }) menu: DynamicDirective<MenuAComponent>;
    
    constructor(
        private dynamicF: DynamicFactory,
        private routeMenuF: RouteMenuFactory,
    ) { }
    
    ngAfterViewInit(): void {
        this.menu.component = this.dynamicF.create(this.menu, MenuAComponent, this.routeMenuF);
    }
}
```
1. 注入依賴共用元件的工廠 RouteMenuFactory。
    `private routeMenuF: RouteMenuFactory`
2. 在使用 this.dynamicF.create() 時，第三個參數放工廠的參數。
    `this.menu.component = this.dynamicF.create(this.menu, MenuBComponent, this.routeMenuF);`


### 小試身手
- 請依上方的方式在 page01.component.ts 動態產生 TableAComponent。