跳到主要內容

Angular : Dynamic Component


上一篇文章中,我們使用Reactive Form動態產生表單欄位,但有時候我們難免也會遇到需要動態產生Component的需求。舉例來說,各位如果有用過Google Keep,它的每一個Note就是一個獨立的Component,當每次想要新增一個Note,勢必需要以動態的方式來產生Note Component。

這次我們以Note例子示範使用ComponentFactoryResolver動態產生Component,其重點流程如下:
1. 新增Note Component,並且在NgModule裡面加入entryComponents: [NoteComponent]。

import { Component, OnInit} from '@angular/core';
import { FormGroup, FormControl} from '@angular/forms';
@Component({
  selector: 'app-note',
  templateUrl: './note.component.html',
  styleUrls: ['./note.component.css']
})
export class NoteComponent implements OnInit {
  Form: FormGroup;
  NoteID: number;
  constructor( ) {
  }
  ngOnInit() {
    this.InitForm();
  }
  InitForm(): void {
    this.Form = new FormGroup({
      Title: new FormControl(null),
      Content: new FormControl(null)
    });
  }
}

<div class="col-sm-12">
  <div class="card">
    <div class="card-header font-weight-bold text-white bg-info">
      {{'Note['+this.NoteID+']'}}
    </div>
    <div class="card-body">
      <form [formGroup]="this.Form" class="form-horizontal" role="form">
        <div class="form-row">
          <div class="form-group col-sm-12">
            <h5 style="display:inline">
              <span class="badge badge-info col-sm-12 text-left">Title
                <span style="color:crimson">*</span>
              </span>
            </h5>
            <input formControlName="Title" class="form-control" type="text" />
          </div>
        </div>
        <div class="form-row">
          <div class="form-group col-sm-12">
            <h5 style="display:inline">
              <span class="badge badge-info col-sm-12 text-left">Content
                <span style="color:crimson">*</span>
              </span>
            </h5>
            <textarea formControlName="Content" class="form-control" rows="10"></textarea>
          </div>
        </div>
      </form>
    </div>
    <div class="card-footer">
      <button type="button" [attr.noteid]="this.NoteID" class="btn btn-secondary col-sm-3 offset-9">Close</button>
    </div>
  </div>
</div>

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { SampleCodeRoutingModule } from './sample-code-routing.module';
import { DynamicComponentSampleComponent } from './dynamic-component-sample/dynamic-component-sample.component';
import { NoteComponent } from './note/note.component';
@NgModule({
  imports: [
    CommonModule,
    ReactiveFormsModule,
    FormsModule,
    SampleCodeRoutingModule
  ],
  declarations: [ DynamicComponentSampleComponent, NoteComponent],
  exports: [DynamicComponentSampleComponent, NoteComponent],
  entryComponents: [NoteComponent]
})
export class SampleCodeModule { }
2. 新增Parent Component,以讓我們在該Component動態產生Note Component。
3. 在Template加入<ng-container>作為Note Component的容器。
4. 新增AddNoteComponent方法,並使用@ViewChild、ViewContainerRef以及ComponentFactoryResolver建立Note Component。
5. 如果想要綁定Note Component的Event,必須在其DOM Render 出來後才能綁定Event,所以需要在ngAfterViewChecked這個生命週期中做綁定Event的動作。

import {
  Component, OnInit, ComponentFactoryResolver,
  ViewContainerRef, ViewChild, ComponentRef, AfterViewChecked
} from '@angular/core';
import { NoteComponent } from '../note/note.component';
import * as _ from 'lodash';
import { fromEvent, pipe, Subscription } from 'rxjs';
declare var $: any;
declare var toastr: any;
@Component({
  selector: 'app-dynamic-component-sample',
  templateUrl: './dynamic-component-sample.component.html',
  styleUrls: ['./dynamic-component-sample.component.css']
})
export class DynamicComponentSampleComponent implements OnInit, AfterViewChecked {
  @ViewChild('Container', { read: ViewContainerRef }) _Container: ViewContainerRef;
  private _NoteComponents = [];
  private _NoteComponentCount = 0;
  private _CurNoteComponent: NoteComponent;
  constructor(private componentFactory: ComponentFactoryResolver) {
  }
  ngOnInit() {
    this.AddNoteComponent();
  }
  AddNoteComponent(): void {
    const componentFactory = this.componentFactory.resolveComponentFactory(NoteComponent);
    const componentRef: ComponentRef = this._Container.createComponent(componentFactory);
    this._CurNoteComponent = componentRef.instance;
    this._CurNoteComponent.NoteID = this._NoteComponentCount;
    this._NoteComponentCount++;
  }
  GetAllNotes() {
    const dataSet: any[] = [];
    for (const i of this._NoteComponents) {
      dataSet.push(i.Component.Form.value);
    }
    toastr.options = {
      positionClass: 'toast-bottom-left'
    };
    toastr.warning(JSON.stringify(dataSet));
  }
  ngAfterViewChecked(): void {
    // tslint:disable-next-line:triple-equals
    if (!_.find(this._NoteComponents, (i) => i.NoteComponent.NoteID as number == this._CurNoteComponent.NoteID)) {
      this._NoteComponents.push({
        NoteComponent: this._CurNoteComponent,
        DelNoteComponentEvent: fromEvent(
          $('button[noteid="' + this._CurNoteComponent.NoteID + '"]'), 'click')
          .subscribe((e: MouseEvent) => {
            const noteID: number = $(e.target).attr('noteid') as number;
            $.each($('button[noteid]'), (index, val) => {
              // tslint:disable-next-line:triple-equals
              if ($(val).attr('noteid') as number == noteID) {
                // tslint:disable-next-line:triple-equals
                this._NoteComponents = this._NoteComponents.filter((i) => i.NoteComponent.NoteID as number != noteID);
                this._Container.remove(index);
                return false;
              }
            });
          })
      });
    }
  }
}

<div class="container-fluid">
  <div class="row justify-content-center">
    <div class="w-100">
      <ng-container #Container>
      </ng-container>
    </div>
  </div>
</div>
<button type="button" class="btn btn-sm btn-warning text-white" style="position: fixed; z-index: 1;
right:80px; top:90vh;width: 50px; height: 50px; border-radius: 50px;" (click)="this.GetAllNotes()">
  <i class="fa fa-info fa-2x" style="margin:3px 0px"></i>
</button>
<button type="button" class="btn btn-sm btn-danger text-white" style="position: fixed; z-index: 1;
right:20px; top:90vh;width: 50px; height: 50px; border-radius: 50px;" (click)="this.AddNoteComponent()">
  <i class="fa fa-plus fa-2x" style="margin:3px 0px"></i>
</button>
Source Code : https://github.com/LiChengJhe/DynamicComponentSampleCode-Angular

留言

這個網誌中的熱門文章

reCAPTCHA v3 的簡單教學

Google於10/29正式發布reCAPTCHA v3 API,該版本中最大的亮點就是透過分析使用者瀏覽網站的行為,以辨識使用者是否為機器人。換句話說,使用者再也不用一直回答問題和不停地點選圖片,所以說以後再也不用聽到客戶抱怨老是點錯圖片,果然科技始於人性啊~ 另外reCAPTCHA v3 的使用方式也非常簡單,只要短短幾個步驟,就能輕鬆地將reCAPTCHA v3運用在專案中。 1. 首先,至  https://www.google.com/recaptcha/admin  替你的網站註冊一個reCAPTCHA。 2. 註冊後,將會得到Site Key和Secret Key,其中Site Key是給前端使用,而Secret Key則是給後端使用。 3. 在前端HTML head的部分加入reCAPTCHA的api.js。 <script src='https://www.google.com/recaptcha/api.js?render=6Lc5vHgUAAAAAKHxlH0FdDJdA2-8yfzpoMDLIWPc'></script> 4. 接著在body的部分呼叫reCAPTCHA API向Google取得token後,再將token送至後端進行驗證,範例如下: <script> grecaptcha.ready(function () { console.log('1. grecaptcha.ready'); console.log('2. grecaptcha.execute("6Lc5vHgUAAAAAKHxlH0FdDJdA2-8yfzpoMDLIWPc", { action: "@Url.Action("VerifyBot", "Account")" })'); grecaptcha.execute('6Lc5vHgUAAAAAKHxlH0FdDJdA2-8yfzpoMDLIWPc', { action: '@

MongoDB: Save Files Using GridFS

過去我們在使用File System,我們必須自己處理備份、複製、擴充的問題;如今我們可以我們可以使用MongoDB作為File DB,它可以利用Replica和Sharing的機制幫助我們解決備份、複製、動態擴充、分散式儲存、自動平衡、故障回復的問題,且效能優於RDBMS。若真要說MongoDB這類NoSql的缺點就是它不能處理Transaction。 在MongoDB中對大於16MB BSON Document(如:圖片、音頻、影片等)是使用GridFS的方式做儲存。 GridFS是一種在MongoDB中存儲大二進製文件的機制,GridFS 會將文件分割成多個Chunk(預設256 KB),而GridFS使用fs.files和fs.chunks等兩個Collection來存儲檔案資料,其中fs.files負責存放文件的名稱、大小、上傳的時間等資訊,而fs.chunks則是負責存放經過分割後的Chunks,其優點是透過分割儲存的方式能夠快速讀取檔案中任何的片段。 fs.files Collection { "_id" : , // 檔案的Unique ID "filename": data_string, //檔案名稱 "length" : data_number, // 檔案大小 "chunkSize" : data_number, // chunk大小,預設256k "uploadDate" : data_date, // 儲存時間 "md5" : data_string //檔案的md5值 } fs.chunks Collection { "_id" : , // 檔案chunk的Unique ID "files_id" : , //對應檔案的Unique ID "n" : chunk_number, // 檔案chunk的數量 "data" : data_binary, // 以二進為儲存檔案 } 在下面例子,我們將簡單地示範使用.NET MongoDB Driver來存取與操作MongoDB的G

Upload large files to web api using resumable.js

最近工作剛好遇到大檔案(> 2GB)上傳的需求,大檔案的上傳方式與平常處裡小檔案上傳當方式有些不同,對於大檔案上傳我們需要額外考量三個問題 : 1. 上傳逾時。 2  IIS 不支援超過2BG 的檔案上傳 3. 檔案在上傳過程中中斷。 上述這三個問題可以透過multipart/form-data和chuck 的方式,將檔案分割成無數個chunk,且給予每個chunk編號,如果傳送失敗就重新傳送chunk ,進而實現斷點續傳的功能。 目前市面三個有支援大檔案與斷點續傳的JavaScript Library分別有 Resumable.js 、 Dropzone.js 、jQuery Ajax File Upload,且它們底層皆是使用HTML5 File API,此外它們的作者都有提供Server Side Implementation Sample,而且連TypeScript的定義檔都有,真的都是佛心來著。 接著我們開始介紹如何使用Resumable.js 上傳檔案至Web API(.NET) : Server Side (Web API)的主要流程: 1. 首先,在Web API需要使用MultipartFormDataStreamProvider來取得每一次Resumable.js 所傳過來的chunk,而chunk由FormData和FileData組成,其中FormData記載Resumable.js所定義的chuck欄位,而FileData則存放chunk的檔案內容。 2. 接著從FormData得到這些Resumable.js所定義的chuck欄位resumableFilename、resumableIdentifier、resumableTotalChunks、resumableChunkNumber欄位,並且根據這些欄位我們將chunk儲存在Upload 目錄下。 3. 等待所有chunk都到位,將合併這些chunk並組成完整的檔案。 FileStorageController Class [RoutePrefix("api/FileStorage")] public class FileStorageController : ApiController {