需求
- 提供給管理員配置簡歷表單的功能甘桑。
- 基本思路:將一個個表單項(xiàng)做成組件(例如輸入框,單選框歹叮,復(fù)選框跑杭,圖片,文件咆耿,日期等)德谅,每個組件對應(yīng)一段固定的
json
(包括組件名,placeholder
萨螺,選項(xiàng)個數(shù)窄做,字?jǐn)?shù)限制等等),方便存儲屑迂。每增加一項(xiàng)就向json
數(shù)組中增加一個元素浸策,然后把這段json
存到后端。每個應(yīng)聘者的簡歷其實(shí)就是一份json
惹盼,查看的時候根據(jù)這段json
利用動態(tài)創(chuàng)建的方式渲染出來庸汗。
動態(tài)創(chuàng)建
? Angular提供了ComponentFactoryResolver
,來協(xié)助我們在程序中動態(tài)產(chǎn)生不同的組件手报,而不用死板地把所有的組件都寫到view
中去蚯舱,再根據(jù)條件判斷是否要顯示某個組件,當(dāng)遇到呈現(xiàn)的方式比較復(fù)雜的需求時非常好用掩蛤,寫出來的代碼也會簡潔枉昏,好看很多。例如可配置表單(簡歷揍鸟,問卷)兄裂,還有滾動廣告等。
開始之前阳藻,先介紹幾個對象
-
ViewChild
:一個屬性裝飾器晰奖,用來從模板視圖中獲取對應(yīng)的元素,可以通過模板變量獲取腥泥,獲取時可以通過 read 屬性設(shè)置查詢的條件匾南,就是說可以把此視圖轉(zhuǎn)為不同的實(shí)例。 -
ViewContainerRef
:一個視圖容器蛔外,可以在此上面創(chuàng)建蛆楞、插入溯乒、刪除組件等等。 -
ComponentFactoryResolve
:一個服務(wù)豹爹,動態(tài)加載組件的核心裆悄,這個服務(wù)可以將一個組件實(shí)例呈現(xiàn)到另一個組件視圖上。 -
entryComponents
:這個數(shù)組是用ViewContainerRef.createComponent()
添加的動態(tài)添加的組件臂聋。將它們添加到entryComponents
是告訴編譯器編譯它們并為它們創(chuàng)建Factory
灯帮。路由配置中注冊的組件也自動添加到entryComponents
,因?yàn)?code>router-outlet也使用ViewContainerRef.createComponent()
將路由組件添加到DOM逻住。
- 有了上面,一個簡單的思路便連貫了:特定區(qū)域就是一個視圖容器迎献,可以通過
ViewChild
來實(shí)現(xiàn)獲取和查詢瞎访,然后使用ComponentFactoryResolve
將已聲明未實(shí)例化的組件解析成為可以動態(tài)加載的component
,再將此component
呈現(xiàn)到此前的視圖容器中吁恍。
1. 建立DynamicComponentDirective
? 首先先建立一個directive
扒秸,并注入ViewContainerRef
,ViewContainerRef
是一個視圖容器冀瓦,可以在此上面創(chuàng)建伴奥、插入、刪除組件等等翼闽,代碼如下:
// DynamicComponentDirective.ts
import {Directive, ViewContainerRef} from '@angular/core';
@Directive({
selector: '[dynamicComponent]'
})
export class DynamicComponentDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
? 接著套用這個directive
到需要動態(tài)加載的組件的容器上拾徙,簡單套用<ng-template>
。
<!--動態(tài)產(chǎn)生組件的容器-->
<ng-template dynamicComponent></ng-template>
2. 使用ComponentFactoryResolver
動態(tài)產(chǎn)生組件
? 直接看代碼感局,都加了注釋尼啡。
import {Component, ComponentFactoryResolver, ViewChild} from '@angular/core';
import {DynamicComponentDirective} from './DynamicComponentDirective';
import {SampleComponent} from './sample/sample.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// 使用ViewChild取得要動態(tài)放置Component的directive(componentHost)
@ViewChild(DynamicComponentDirective) componentHost: DynamicComponentDirective;
constructor(
// 注入ComponentFactoryResolver
private componentFactoryResolver: ComponentFactoryResolver
) { }
title = '動態(tài)創(chuàng)建組件樣例';
createNewComponent() {
// 建立ComponentFactory
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(SampleComponent);
const viewContainerRef = this.componentHost.viewContainerRef;
// 產(chǎn)生我們需要的Component并放入componentHost之中
viewContainerRef.createComponent(componentFactory);
// const componentRef = viewContainerRef.createComponent(componentFactory);
}
clearView() {
const viewContainerRef = this.componentHost.viewContainerRef;
viewContainerRef.clear();
}
}
<div style="text-align:center">
<h1>
{{ title }}
</h1>
<!--動態(tài)產(chǎn)生組件的容器-->
<ng-template dynamicComponent></ng-template>
<button (click)="createNewComponent()">動態(tài)創(chuàng)建組件</button>
<button (click)="clearView()">清除視圖</button>
</div>
3. 在Module
中加入entryComponents
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { SampleComponent } from './sample/sample.component';
import {DynamicComponentDirective} from './DynamicComponentDirective';
@NgModule({
declarations: [
AppComponent,
SampleComponent,
DynamicComponentDirective
],
imports: [
BrowserModule
],
entryComponents: [
SampleComponent
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
4. 效果展示
image.png
有了以上知識鋪墊,可以進(jìn)入下一階段询微。
動態(tài)創(chuàng)建表單與表單的渲染
- 要實(shí)現(xiàn)的效果如下:
- 動態(tài)創(chuàng)建一個組件崖瞭,生成一段
json
。 - 根據(jù)
json
數(shù)組生成表單撑毛。
動態(tài)創(chuàng)建組件:
image.png
點(diǎn)擊保存模板书聚,保存json,根據(jù)json重新渲染表單:
image.png
具體步驟如下:
1. 以輸入框?yàn)槔宕疲xjson
格式
// FormJson.ts
export class FormJson {
public static basedata: any = {
name : '', // 子項(xiàng)名稱
id : '',
hintText : '', // 子選項(xiàng)提示
type : '', // 組件類型
numberLimit : '', // 字?jǐn)?shù)限制
content : [], // 存放用戶填寫信息
};
}
2. 創(chuàng)建可配置組件(以輸入框?yàn)槔?/h5>
執(zhí)行ng -g component input
新建一個組件雌续,代碼如下:
<!--input.component.html-->
<!--顯示模板-->
<div>
<label for="input">{{item.name}}</label>
<input
id="input"
[(ngModel)]="item.content"
type="{{item.type}}"
placeholder="{{item.hintText}}"
>
</div>
//input.component.ts
import {Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class InputComponent implements OnInit {
constructor() { }
// 接收管理員配置的參數(shù)
@Input() item: any;
ngOnInit() {
}
}
3. 動態(tài)創(chuàng)建表單
基本操作與動態(tài)創(chuàng)建組件是一樣的,每創(chuàng)建一個新的表單項(xiàng)蹦疑,formJson
數(shù)組就增加一個元素西雀,這里存儲表單模板用的是json
,所以渲染的時候要根據(jù)type
來判斷要創(chuàng)建什么組件歉摧,當(dāng)然我這里只做了個輸入框艇肴,只做示例腔呜,其他的組件可以舉一反三。代碼解析如下:
// app.component.ts
import {Component, ComponentFactoryResolver, ViewChild} from '@angular/core';
import {DynamicComponentDirective} from './share/DynamicComponentDirective';
import {SampleComponent} from './sample/sample.component';
import {FormJson} from './share/FormJson';
import {InputComponent} from './input/input.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// 使用ViewChild取得要動態(tài)放置Component的directive(componentHost)
@ViewChild(DynamicComponentDirective) componentHost: DynamicComponentDirective;
public baseData = JSON.parse(JSON.stringify(FormJson.basedata));
public formJson = [];
public save = false; // 模擬根據(jù)后端json重新加載表單
public formJsonText: string; // json文本
constructor(
// 注入ComponentFactoryResolver
private componentFactoryResolver: ComponentFactoryResolver
) { }
// 動態(tài)創(chuàng)建組件
createInputComponent() {
// 示例類型都是文本輸入框再悼,所以type字段都置為 text
this.baseData.type = 'text';
// 將json插入核畴,完成之后可存到后端
this.formJson.push(this.baseData);
// 頁面顯示json
this.formJsonText = JSON.stringify(this.formJson);
console.log(this.formJson);
// 清除舊預(yù)覽
this.componentHost.viewContainerRef.clear();
// 渲染新示例頁面
this.createForm(this.formJson);
// 將json元素賦空,方便下次創(chuàng)建
this.baseData = JSON.parse(JSON.stringify(FormJson.basedata));
}
// 根據(jù)json動態(tài)創(chuàng)建表單
createForm(formJson) {
const inputComponentFactory = this.componentFactoryResolver.resolveComponentFactory(InputComponent);
// 遍歷json 根據(jù)不同類型創(chuàng)建組件冲九,可擴(kuò)充
for (let i = 0 ; i < formJson.length ; i++) {
const item = formJson[i] ;
let componentRef;
switch (item.type) {
case 'text':
componentRef = this.componentHost.viewContainerRef.createComponent(inputComponentFactory);
componentRef.instance.componentRef = componentRef; // 傳入自身組件引用谤草,用于返回來編輯自身
componentRef.instance.item = item; // 將管理員配置數(shù)據(jù)傳進(jìn)組件渲染
break;
}
}
}
saveForm() {
this.componentHost.viewContainerRef.clear();
// todo 將表單模板存到后端
console.log(this.formJson);
this.save = true;
setTimeout(() => {
// todo 根據(jù)json重新解析,其實(shí)就像預(yù)覽一樣莺奸,調(diào)用createForm
// 延時3s查看效果丑孩,便于理解
this.createForm(this.formJson);
}, 3000);
}
}
<div style="width: 300px; float: left;height: 400px" align="center">
<p>示例:創(chuàng)建自定義輸入框</p>
<div>
<label for="name">輸入框名字:</label>
<input
id="name"
[(ngModel)]="baseData.name"
>
</div>
<div>
<label for="placeholder">輸入框提示:</label>
<input
id="placeholder"
[(ngModel)]="baseData.hintText"
>
</div>
<div align="center" style="margin: 10px">
<button (click)="createInputComponent()">動態(tài)創(chuàng)建組件</button>
</div>
</div>
<div style="width: 300px;margin-left: 500px;height: 400px" align="center">
<div>
<p *ngIf="save === false">示例:預(yù)覽</p>
<div *ngIf="save">
<p>--------JSON重新轉(zhuǎn)化為表單-----------</p>
<p>--------延時讓效果更明顯-----------</p>
</div>
<ng-template dynamicComponent></ng-template>
</div>
<div style="margin: 10px">
<button (click)="saveForm()">保存模板</button>
</div>
</div>
<div style="width: 500px;height: 400px" align="center">
<div>
<p >打印JSON</p>
</div>
<div style="margin: 10px">
<textarea [(ngModel)] = "formJsonText" rows="30" style="width: 500px"></textarea>
</div>
</div>
操作演示:
- 動態(tài)創(chuàng)建一個組件
image.png
- 觀察預(yù)覽和son輸出
image.png
- 點(diǎn)擊生成模板,根據(jù)json重新渲染組件
image.png
最后
源碼地址->Demo
在線示例->在線demo
? 這只是我個人對項(xiàng)目中的用法的總結(jié)灭贷,歡迎大家指正與交流温学。還有就是demo只是個demo,我并沒有花時間去做檢查甚疟、控制之類的仗岖,所以有bug很正常。览妖。