Angular 如何在 innerHTML 中使用 ng-template
How to use ng-template inside innerHTML in Angular
有人可以帮助我,如何在内部HTML 标签中使用 ng-template,
Stackblitz 演示:https://stackblitz.com/edit/angular-xgnnj4
我想做的是,
在组件中HTML
<div [innerHTML]="htmlString"><div>
在组件 TS 中
@ViewChild('dynamicComponent', { static: false, read: ViewContainerRef }) myRef: ViewContainerRef;
htmlString = `<ng-template #dynamicComponent></ng-template>`;
ngAfterViewInit(): void {
const factory = this.componentFactoryResolver.resolveComponentFactory(CommentComponent);
const ref = this.myRef.createComponent(factory);
ref.changeDetectorRef.detectChanges();
}
我真的需要这样实现,因为我想从 API 中获取 HTML 内容并使用该值动态显示 HTML 内容
因为我得到的错误是 无法读取未定义 的 属性 'createComponent' 但是如果我把内部 HTML 放在外面它就可以工作完美
我已经完成了 https://angular.io/guide/dynamic-component-loader,但这对我的场景没有帮助
您可以使用 DomSanitizer 将任何 HTML 传递给模板:
import { DomSanitizer } from '@angular/platform-browser';
...
export class HelloComponent {
htmlString = this.sanitizer.bypassSecurityTrustHtml(`
<ng-template>
<div style="width: 10px; height: 10px; background: red;"></div>
</ng-template>
`);
constructor(private sanitizer: DomSanitizer) {}
}
STACKBLITZ:https://stackblitz.com/edit/angular-t9roxg?file=src%2Fapp%2Fhello.component.ts
请记住,通过这种方式 HTML 将作为普通 HTML 传递,无需任何 Angular 处理
您基本上要做的是在动态加载的字符串中动态加载组件。 HTML 文件中内联或单独提供的任何模板都由 Angular 渲染引擎处理。 (Ivy是从Angular9开始优化的新渲染引擎,从Angular4到8默认渲染引擎叫做Renderer2
这是Renderer
的后继者,它是 Angular 2 到 4 的默认值)。
这基本上就是您在附加的 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
将在 constructor
中编写的 setTimeout
方法之前被调用,因为您定义的 2000ms
超时,所以 myRef
将始终未定义,因为它尚未实例化,我看到的下一个问题是关于您的模板,
<div [innerHTML]="htmlChildString"></div>
innerHTML
不应该包含任何 angular 符号,它只能包含普通的 HTML 可以直接嵌入 DOM 因为它们不会由 Angular 渲染引擎处理,这正是@Andriy 已经指出的。
现在来解释为什么您尝试的是不可能的,假设您定义了一个名为 AComponent
的组件,其模板为:
<span>I am {{name}}</span>
angular 编译器,在遍历每个(静态定义的)组件和附加到这些组件的模板时,它会为每个组件创建工厂。当 angular 编译器通过上面的模板时,它会生成一个如下所示的工厂,
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使用这个工厂来实例化View Definition,后者又被用来创建组件View。在引擎盖下 Angular 将应用程序表示为视图树。每个组件类型只有一个视图定义实例,它充当所有视图的模板。但是对于每个组件实例 Angular 创建一个单独的视图。
以上工厂描述了组件视图的结构,并在实例化组件时使用。 jit_viewDef1
是对创建视图定义的 viewDef
函数的引用。
视图定义接收视图定义节点作为参数,类似于 html 的结构,但也包含许多 angular 具体细节。
这个工厂是从 resolveComponentFactory
方法返回的,因为你在你的模块中静态声明了 ChildViewComponent
并将它标记为 entryComponent
,这就是 [=92] =] 决定预先生成其工厂,即使它知道它当前未被 Router 或其他组件的模板使用。
类似地,ViewContainerRef
是对视图容器(ng-template
或 ng-container
)的引用,它必须直接在模板中静态定义,或者应该可以通过ngSwitch
或 ngIf
指令的组合,但在任何情况下,编译器/渲染引擎在编译时都必须事先知道此容器的存在。
在你的情况下,当你通过 API 动态加载这些 angular 符号时,angular 只知道这是一个简单的字符串,并且事先不知道这是一些可以在 运行 时间包含视图的东西。如果您看过 Angular 生成的构建,它只包含 JS 文件,没有 HTML 模板或 CSS 与您的组件对应的样式文件。这些 HTML 文件中包含的每个模板都被转换为最终在 运行 时间使用的工厂方法。
我推荐你的是,
- 要么仅加载纯粹的 HTML 内容而不是其余内容 API 并将所有 angular 特定符号拉入您的 angular 应用程序并有条件地加载它们,
例如看看这个 stackBlitz 里面没有开箱即用的东西,它只是使用 CDK Portal,它基本上只是方法
resolveComponentFactory
和 createComponent
的抽象以及静态定义的templateRef
单击按钮时动态加载到 viewContainerRef
。
- 或者创建另一个 JS 或 Angular 应用程序并在其中拉取
ChildViewComponent
并让 Iframe 根据需要根据任何条件加载内容,
- 加载模板的编译版本,类似于 angular 延迟加载其模块的方式,以及一些如何以编程方式将其加载到路由器插座中的方式。我不知道这是否可能,这只是我的一个想法,但我不希望这是直截了当的。
有人可以帮助我,如何在内部HTML 标签中使用 ng-template,
Stackblitz 演示:https://stackblitz.com/edit/angular-xgnnj4
我想做的是,
在组件中HTML
<div [innerHTML]="htmlString"><div>
在组件 TS 中
@ViewChild('dynamicComponent', { static: false, read: ViewContainerRef }) myRef: ViewContainerRef;
htmlString = `<ng-template #dynamicComponent></ng-template>`;
ngAfterViewInit(): void {
const factory = this.componentFactoryResolver.resolveComponentFactory(CommentComponent);
const ref = this.myRef.createComponent(factory);
ref.changeDetectorRef.detectChanges();
}
我真的需要这样实现,因为我想从 API 中获取 HTML 内容并使用该值动态显示 HTML 内容
因为我得到的错误是 无法读取未定义 的 属性 'createComponent' 但是如果我把内部 HTML 放在外面它就可以工作完美
我已经完成了 https://angular.io/guide/dynamic-component-loader,但这对我的场景没有帮助
您可以使用 DomSanitizer 将任何 HTML 传递给模板:
import { DomSanitizer } from '@angular/platform-browser';
...
export class HelloComponent {
htmlString = this.sanitizer.bypassSecurityTrustHtml(`
<ng-template>
<div style="width: 10px; height: 10px; background: red;"></div>
</ng-template>
`);
constructor(private sanitizer: DomSanitizer) {}
}
STACKBLITZ:https://stackblitz.com/edit/angular-t9roxg?file=src%2Fapp%2Fhello.component.ts
请记住,通过这种方式 HTML 将作为普通 HTML 传递,无需任何 Angular 处理
您基本上要做的是在动态加载的字符串中动态加载组件。 HTML 文件中内联或单独提供的任何模板都由 Angular 渲染引擎处理。 (Ivy是从Angular9开始优化的新渲染引擎,从Angular4到8默认渲染引擎叫做Renderer2
这是Renderer
的后继者,它是 Angular 2 到 4 的默认值)。
这基本上就是您在附加的 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
将在 constructor
中编写的 setTimeout
方法之前被调用,因为您定义的 2000ms
超时,所以 myRef
将始终未定义,因为它尚未实例化,我看到的下一个问题是关于您的模板,
<div [innerHTML]="htmlChildString"></div>
innerHTML
不应该包含任何 angular 符号,它只能包含普通的 HTML 可以直接嵌入 DOM 因为它们不会由 Angular 渲染引擎处理,这正是@Andriy 已经指出的。
现在来解释为什么您尝试的是不可能的,假设您定义了一个名为 AComponent
的组件,其模板为:
<span>I am {{name}}</span>
angular 编译器,在遍历每个(静态定义的)组件和附加到这些组件的模板时,它会为每个组件创建工厂。当 angular 编译器通过上面的模板时,它会生成一个如下所示的工厂,
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使用这个工厂来实例化View Definition,后者又被用来创建组件View。在引擎盖下 Angular 将应用程序表示为视图树。每个组件类型只有一个视图定义实例,它充当所有视图的模板。但是对于每个组件实例 Angular 创建一个单独的视图。
以上工厂描述了组件视图的结构,并在实例化组件时使用。 jit_viewDef1
是对创建视图定义的 viewDef
函数的引用。
视图定义接收视图定义节点作为参数,类似于 html 的结构,但也包含许多 angular 具体细节。
这个工厂是从 resolveComponentFactory
方法返回的,因为你在你的模块中静态声明了 ChildViewComponent
并将它标记为 entryComponent
,这就是 [=92] =] 决定预先生成其工厂,即使它知道它当前未被 Router 或其他组件的模板使用。
类似地,ViewContainerRef
是对视图容器(ng-template
或 ng-container
)的引用,它必须直接在模板中静态定义,或者应该可以通过ngSwitch
或 ngIf
指令的组合,但在任何情况下,编译器/渲染引擎在编译时都必须事先知道此容器的存在。
在你的情况下,当你通过 API 动态加载这些 angular 符号时,angular 只知道这是一个简单的字符串,并且事先不知道这是一些可以在 运行 时间包含视图的东西。如果您看过 Angular 生成的构建,它只包含 JS 文件,没有 HTML 模板或 CSS 与您的组件对应的样式文件。这些 HTML 文件中包含的每个模板都被转换为最终在 运行 时间使用的工厂方法。
我推荐你的是,
- 要么仅加载纯粹的 HTML 内容而不是其余内容 API 并将所有 angular 特定符号拉入您的 angular 应用程序并有条件地加载它们,
例如看看这个 stackBlitz 里面没有开箱即用的东西,它只是使用 CDK Portal,它基本上只是方法
resolveComponentFactory
和createComponent
的抽象以及静态定义的templateRef
单击按钮时动态加载到viewContainerRef
。
- 或者创建另一个 JS 或 Angular 应用程序并在其中拉取
ChildViewComponent
并让 Iframe 根据需要根据任何条件加载内容, - 加载模板的编译版本,类似于 angular 延迟加载其模块的方式,以及一些如何以编程方式将其加载到路由器插座中的方式。我不知道这是否可能,这只是我的一个想法,但我不希望这是直截了当的。