需求
- 提供给管理员配置简历表单的功能。
- 基本思路:将一个个表单项做成组件(例如输入框,单选框,复选框,图片,文件,日期等),每个组件对应一段固定的
json
(包括组件名,placeholder
,选项个数,字数限制等等),方便存储。每增加一项就向json
数组中增加一个元素,然后把这段json
存到后端。每个应聘者的简历其实就是一份json
,查看的时候根据这段json
利用动态创建的方式渲染出来。
动态创建
Angular提供了ComponentFactoryResolver
,来协助我们在程序中动态产生不同的组件,而不用死板地把所有的组件都写到view
中去,再根据条件判断是否要显示某个组件,当遇到呈现的方式比较复杂的需求时非常好用,写出来的代码也会简洁,好看很多。例如可配置表单(简历,问卷),还有滚动广告等。
开始之前,先介绍几个对象
-
ViewChild
:一个属性装饰器,用来从模板视图中获取对应的元素,可以通过模板变量获取,获取时可以通过 read 属性设置查询的条件,就是说可以把此视图转为不同的实例。 -
ViewContainerRef
:一个视图容器,可以在此上面创建、插入、删除组件等等。 -
ComponentFactoryResolve
:一个服务,动态加载组件的核心,这个服务可以将一个组件实例呈现到另一个组件视图上。 -
entryComponents
:这个数组是用ViewContainerRef.createComponent()
添加的动态添加的组件。将它们添加到entryComponents
是告诉编译器编译它们并为它们创建Factory
。路由配置中注册的组件也自动添加到entryComponents
,因为router-outlet
也使用ViewContainerRef.createComponent()
将路由组件添加到DOM。
- 有了上面,一个简单的思路便连贯了:特定区域就是一个视图容器,可以通过
ViewChild
来实现获取和查询,然后使用ComponentFactoryResolve
将已声明未实例化的组件解析成为可以动态加载的component
,再将此component
呈现到此前的视图容器中。
1. 建立DynamicComponentDirective
首先先建立一个directive
,并注入ViewContainerRef
,ViewContainerRef
是一个视图容器,可以在此上面创建、插入、删除组件等等,代码如下:
// DynamicComponentDirective.ts
import {Directive, ViewContainerRef} from '@angular/core';
@Directive({
selector: '[dynamicComponent]'
})
export class DynamicComponentDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
接着套用这个directive
到需要动态加载的组件的容器上,简单套用<ng-template>
。
<!--动态产生组件的容器-->
<ng-template dynamicComponent></ng-template>
2. 使用ComponentFactoryResolver
动态产生组件
直接看代码,都加了注释。
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取得要动态放置Component的directive(componentHost)
@ViewChild(DynamicComponentDirective) componentHost: DynamicComponentDirective;
constructor(
// 注入ComponentFactoryResolver
private componentFactoryResolver: ComponentFactoryResolver
) { }
title = '动态创建组件样例';
createNewComponent() {
// 建立ComponentFactory
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(SampleComponent);
const viewContainerRef = this.componentHost.viewContainerRef;
// 产生我们需要的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>
<!--动态产生组件的容器-->
<ng-template dynamicComponent></ng-template>
<button (click)="createNewComponent()">动态创建组件</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. 效果展示
有了以上知识铺垫,可以进入下一阶段。
动态创建表单与表单的渲染
- 要实现的效果如下:
- 动态创建一个组件,生成一段
json
。 - 根据
json
数组生成表单。
动态创建组件:
点击保存模板,保存json,根据json重新渲染表单:
具体步骤如下:
1. 以输入框为例,定义json
格式
// FormJson.ts
export class FormJson {
public static basedata: any = {
name : '', // 子项名称
id : '',
hintText : '', // 子选项提示
type : '', // 组件类型
numberLimit : '', // 字数限制
content : [], // 存放用户填写信息
};
}
2. 创建可配置组件(以输入框为例)
执行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() { }
// 接收管理员配置的参数
@Input() item: any;
ngOnInit() {
}
}
3. 动态创建表单
基本操作与动态创建组件是一样的,每创建一个新的表单项,formJson
数组就增加一个元素,这里存储表单模板用的是json
,所以渲染的时候要根据type
来判断要创建什么组件,当然我这里只做了个输入框,只做示例,其他的组件可以举一反三。代码解析如下:
// 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取得要动态放置Component的directive(componentHost)
@ViewChild(DynamicComponentDirective) componentHost: DynamicComponentDirective;
public baseData = JSON.parse(JSON.stringify(FormJson.basedata));
public formJson = [];
public save = false; // 模拟根据后端json重新加载表单
public formJsonText: string; // json文本
constructor(
// 注入ComponentFactoryResolver
private componentFactoryResolver: ComponentFactoryResolver
) { }
// 动态创建组件
createInputComponent() {
// 示例类型都是文本输入框,所以type字段都置为 text
this.baseData.type = 'text';
// 将json插入,完成之后可存到后端
this.formJson.push(this.baseData);
// 页面显示json
this.formJsonText = JSON.stringify(this.formJson);
console.log(this.formJson);
// 清除旧预览
this.componentHost.viewContainerRef.clear();
// 渲染新示例页面
this.createForm(this.formJson);
// 将json元素赋空,方便下次创建
this.baseData = JSON.parse(JSON.stringify(FormJson.basedata));
}
// 根据json动态创建表单
createForm(formJson) {
const inputComponentFactory = this.componentFactoryResolver.resolveComponentFactory(InputComponent);
// 遍历json 根据不同类型创建组件,可扩充
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; // 将管理员配置数据传进组件渲染
break;
}
}
}
saveForm() {
this.componentHost.viewContainerRef.clear();
// todo 将表单模板存到后端
console.log(this.formJson);
this.save = true;
setTimeout(() => {
// todo 根据json重新解析,其实就像预览一样,调用createForm
// 延时3s查看效果,便于理解
this.createForm(this.formJson);
}, 3000);
}
}
<div style="width: 300px; float: left;height: 400px" align="center">
<p>示例:创建自定义输入框</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()">动态创建组件</button>
</div>
</div>
<div style="width: 300px;margin-left: 500px;height: 400px" align="center">
<div>
<p *ngIf="save === false">示例:预览</p>
<div *ngIf="save">
<p>--------JSON重新转化为表单-----------</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>
操作演示:
- 动态创建一个组件
- 观察预览和son输出
- 点击生成模板,根据json重新渲染组件
最后
源码地址->Demo
在线示例->在线demo
这只是我个人对项目中的用法的总结,欢迎大家指正与交流。还有就是demo只是个demo,我并没有花时间去做检查、控制之类的,所以有bug很正常。。