您基本上要做的是在动态加载的字符串中动态加载组件。Angular 渲染引擎处理内联或单独在 HTML 文件中提供的任何模板。(Ivy是从 Angular 9 开始的新的优化渲染引擎,从 Angular 4 到 8 调用默认渲染引擎,它是 Angular 2 到 4 默认Renderer2
的继承者)。Renderer
这基本上就是您在附加的 stackBlitz 中尝试做的事情,
@ViewChild("dynamicComponent", { static: false, read: ViewContainerRef }) myRef: ViewContainerRef;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
//Simulate the network call
setTimeout(() => {
this.htmlChildString = `<ng-template #dynamicComponent></ng-template>`;
}, 2000);
}
ngAfterViewInit(): void {
const factory = this.componentFactoryResolver.resolveComponentFactory(
ChildViewComponent
);
const ref = this.myRef.createComponent(factory);
ref.changeDetectorRef.detectChanges();
}
这里错误的第一件事是,由于您定义的超时,显然ngAfterViewInit
会在您的setTimeout
方法写入之前被调用,因此将始终未定义,因为它尚未实例化,我看到的下一个问题是关于您的模板,constructor
2000ms
myRef
<div [innerHTML]="htmlChildString"></div>
innerHTML
不应该包含任何角度符号,它只能包含可以直接嵌入到 DOM 中的纯 HTML,因为它们不会被 Angular 渲染引擎处理,这正是 @Andriy 已经指出的。
现在来了解您尝试的原因是不可能的,假设您定义了一个名为AComponent
具有模板的组件:
<span>I am {{name}}</span>
角度编译器,当遍历每个(静态定义的)组件和附加到这些组件的模板时,它会为每个组件创建工厂。当角度编译器通过上面的模板时,它会生成一个工厂,如下所示,
function View_AComponent_0(l) {
return jit_viewDef1(0,
[
jit_elementDef2(0,null,null,1,'span',...),
jit_textDef3(null,['I am ',...])
],
null,
function(_ck,_v) {
var _co = _v.component;
var currVal_0 = _co.name;
_ck(_v,1,0,currVal_0);
注意下一行?
jit_elementDef2(0,null,null,1,'span',...),
Angular 正在做的是使用 JS 来创建元素,Angular 使用这个工厂来实例化视图定义,而视图定义又用于创建组件视图。在引擎盖下,Angular 将应用程序表示为视图树。每个组件类型只有一个视图定义实例,它充当所有视图的模板。但是对于每个组件实例,Angular 都会创建一个单独的视图。
上面的工厂描述了组件视图的结构,并在实例化组件时使用。是对创建视图定义的函数的jit_viewDef1
引用。viewDef
视图定义接收视图定义节点作为参数,这些参数类似于 html 的结构,但也包含许多角度特定的细节。
这个工厂是从resolveComponentFactory
方法返回的,因为您已经在模块中静态声明ChildViewComponent
并将其标记为entryComponent
,这就是 Angular 决定预先生成其工厂的原因,即使它知道它当前没有被路由器或其他组件的模板使用.
同样ViewContainerRef
是对视图容器 (ng-template
或ng-container
) 的引用,它必须直接在模板中静态定义,或者应该可以通过ngSwitch
或ngIf
指令的组合遇到,但无论如何编译器/渲染引擎必须知道在编译时此容器的存在。
在您的情况下,当您通过 API 动态加载这些角度符号时,角度只知道这是一个简单的字符串,并且事先不知道这是可以在运行时包含视图的东西。如果你看过 Angular 生成的构建,它只包含 JS 文件,没有与你的组件对应的 HTML 模板或 CSS 样式文件。这些 HTML 文件中包含的每个模板都被转换为最终在运行时使用的工厂方法。
我建议你的是,
- 要么仅在其余 API 上加载纯 HTML 内容,然后将所有角度特定的符号拉入您的角度应用程序并有条件地加载它们,例如看看这个stackBlitz里面没有开箱即用的东西,它只是使用 CDK Portal 这是基本上只是对方法的抽象
resolveComponentFactory
和createComponent
静态定义,在单击按钮时templateRef
动态加载。viewContainerRef
- 或者创建另一个 JS 或 Angular 应用程序并拉
ChildViewComponent
入其中并根据需要让 iframe 根据任何条件加载内容,
- 加载模板的编译版本,类似于 angularlazy 加载其模块的方式,以及一些如何以编程方式将其加载到路由器插座中。我不知道这是否可能,这只是我心中的一个想法,但我不希望这是直截了当的。