angular2(4) 中动态创建组件的两种方案

上例子:https://github.com/wuzhouyang/angular-dynamic-component-example

接触 angular2 也有好几月了,由于在做的项目dom的操作貌似比较频繁,而且并不能针对dom来编程,这样的话与模板的耦合度便十分大,所以只能针对组件编程,动态加载组件来实现项目中相关的功能,那就不得不用到angular2中一些比较低等级的api。此文要记录的便是目前用到的两种动态创建组件的方案。

(一)动态加载已经声明的组件

针对我的项目场景:用户拖动相关的块到特定区域,区域中便会生成相应的UI控件,此UI控件有自己的模板、行为等等。生成UI 后便会在区域中显示出来。所以我觉得将UI控件封为一个小组件,再动态加载,是个不错的方案。angular2中是如何动态加载组件的?

要实现这个功能,得先简单了解angular2 中相关的api

  • ViewChild:一个属性装饰器,用来从模板视图中获取对应的元素,可以通过模板变量获取,获取时可以通过 read 属性设置查询的条件,就是说可以把此视图转为不同的实例
  • ViewContainerRef :一个视图容器,可以在此上面创建、插入、删除组件等等
  • ComponentFactoryResolve:一个服务,动态加载组件的核心,这个服务可以将一个组件实例呈现到另一个组件视图上

有了这三个,一个简单的思路便连贯了:特定区域就是一个视图容器,可以通过 ViewChild 来实现获取和查询,然后使用ComponentFactoryResolve将已声明未实例化的组件解析成为可以动态加载的 component,再将此component 呈现到此前的视图容器中

为了实现此功能,我写了一个简单的例子
我单独写了一个特性模块来测试,下面是结构图

例子模块

特性模块涉及到 lazyload 的知识,我也不多讲了。直接看结构说明

  • dy1.component.ts
  • dy2.component.ts

这两个就是将要动态加载的组件,内容不多,就是为了测试,如图

dy1.component.ts
dy2.component.ts
  • dynamic.component.ts

这个是此特性模块对应的组件实例,用于声明一些逻辑动作。简单看看此组件代码

dynamic.component.ts

可以看到在顶部导入处必需的三个都在。在组件类的顶部我通过模板变量的方式获取了此组件模板视图上了一个元素来作为视图容器,可以看看模板的代码

dynamic.component.ts

红框处便是我们要在上面动态加载组件的容器。
看看获取容器的代码

dynamic.component.ts

通过模板变量名获取,然后 可以通过 read 选项设置为一个 ViewContainerRef ,最终在生命钩期 ngAfterViewInit 过后便会获取此区域的一个 ViewContainerRef 实例。
看看主要的加载组件函数

dynamic.component.ts

我们已经通过组件类的构造函数注入了ComponentFactoryResolve服务,现在便可以调用其方法来解析得到一个 componentFactory 了,resolveComponentFactory 解析一个已经声明的组件得到一个可动态加载的 componentFactory,这里的DY1Component我们已经在顶部导入了。然后我们可以直接调用容器的createComponent函数将解析出来的componentFactory动态呈现到容器视图上。

然后我们就可以开心的运行、点击 ”动态加载组件“ 的按钮了。。
不开心的是,报错了,相信我们可以得到这样的报错

error

原来动态加载的组件必须声明在特性模块的 entryComponents 中,下面是angular官网对于entryComponents的说明

Specifies a list of components that should be compiled when this module is defined. For each component listed here, Angular will create a ComponentFactory
and store it in the ComponentFactoryResolver
.

就是说此处声明的组件 Angular 都会创建一个ComponentFactory并将其存储在ComponentFactoryResolver中,也就是动态加载必需的步骤。
所以我们将其加到特性模块 entryComponents

dynamicModule.module.ts

然后我们又可以开心地运行,点击了。。。
然而这次我们又得到另一个错误了

error

意思就是DY1Component 还没有声明 — — ||| 。 所以还需要将要动态加载的组件声明为是此特性模块的组件,如图

dynamicModule.module.ts

然后这次可以开心的看例子了

demo

对于这个方案,上面展示的是最简单的一面,没有考虑到项目的优化还有代码的简洁。项目的优化问题体现在后期项目的压缩和预编译中,如果我们是想要通过组件的名字来动态加载组件,可能在优化有组件的名字都被统一压缩成一个字母,所以会导致找不到组件的问题;代码的简洁呢因人而异,我是喜欢保持根 module 的简洁,所以有必要另起文件来作为媒介。

说一说改进的方式
新建一个管理类

comMgr.ts

导入动态加载的组件,声明一个供名字获取组件的变量,还有一个供根模块声明的变量

comMgr.ts

接下来就可以修改之前的引用代码了:

dynamicModule.module.ts
dynamic.component.ts

可以看到当动态组件多的时候,根 module还是能保持简洁。并且在根组件中可以通过名字获取对应的组件,不怕后期项目优化的影响。

(二)动态创建模块的方式来加载动态创建的组件

不同的需求有不同的方案,对于上面的需求与方案无疑是很适合的,但是需要我们先创建好组件,再声明到根 module 中,至少缺失了一些灵活性。
现在我又有了另外一个项目场景:我拖动生成了UI 控件,只是为了展示对应的样式,UI控件是需要第三方环境支持的,所以我需要加工拖动的数据,抛给后台处理,后台返回包含表示模板、组件的字符串的 JSON 数据回来,然后我通知另外一个网站,此网站包含了第三方环境,让他们去动态创建这些组件。一句话概括,就是我要动态创建不存在的组件而不是已经声明的组件。
要完成动态创建组件的,我们得先看看相关的api

  • Compiler:用于在运行时运行angular编译器来创建 ComponentFactory 的服务,然后可以使用它来创建和呈现组件实例

其实最简单的了解这一个就能实现了,我们知道 容器创建和呈现组件的函数需要一个 ComponentFactory,而Compiler能够在运行时动态创建一个ComponentFactory,那就十分符合需求了。

我新增了一些代码

dynamic.component.ts
dynamic.component.ts
dynamic.component.ts

这些都是在根组件中的代码,引入了 Compiler 服务。新增一个 createModule函数,通过 ComponentNgModule 修饰器动态创建新的组件和模块,然后调用 CompilercompileModuleAndAllComponentsSync 方法获取一个新的ComponentFactory。然后容器的呈现还是一样,直接 createComponent。在模板的按钮中设置对应的动作后就可以开心的运行和点击了。。

效果

这次十分顺利,没有任何报错。
可能我们会有疑惑,这个不也还是事先声明了动态的组件。
其实只是我这个例子展示的问题,这里的 模板、组件类都是可以动态当成参数设置的,下面此图是我在项目中用到的

项目

aot build的问题

使用了这种方案,在项目正常开发预览的时候是没有任何问题的,但是当我构建项目(aot 等)运行的时候,就会出现下面报错

error

看错误可以知道在项目构建后是缺失angular编译器的,原因就是使用了AOT后已经是预编译了,编译器就会从中移除。为了解决这一问题,我们可以在这个把 compiler 编译器在构建的时候打到当前的特性包中

dynamic.component.ts
dynamic.component.ts

通过此处代码就可以将一个angular 编译器打到当前包中。再次运行build构建,可以看到已经没有之前的错误了,但是缺因此得到另一个错误

error

此错误暂时没能解决,待更新!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容

  • 这篇文章我们将介绍在 Angular 中如何动态创建组件。 定义 AlertComponent 组件 首先,我们需...
    semlinker阅读 1,596评论 0 5
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,778评论 6 342
  • 版本:Angular 5.0.0-alpha AngularDart(本文档中我们通常简称 Angular ) 是...
    soojade阅读 825评论 0 4
  • 第三章第四章对比第一周第一章第二章的内容更有厚度。有经典理论支撑,有正面例子反面例子,帮助读者理解。干货多多!收获...
    freya_c阅读 153评论 0 0
  • 北京有很多烧香拜佛的地方,大概是浓厚的古文化和深沉的历史为这座城市留下的积淀。 作为一个不信佛与道的人,我却在两日...
    386035e48425阅读 631评论 0 3