如何在 Angular 的 innerHTML 中使用 ng-template

Ron*_*Ron 0 innerhtml angular2-template ng-template angular angular-dynamic-components

有人可以帮助我,如何在innerHTML 标签中使用ng-template,

Stackblitz 演示:https ://stackblitz.com/edit/angular-xgnnj4

我想做的是,

在组件 HTML 中

<div [innerHTML]="htmlString"><div>
Run Code Online (Sandbox Code Playgroud)

在组件 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();
  }
Run Code Online (Sandbox Code Playgroud)

我真的需要以这种方式实现,因为我想从 API 获取 HTML 内容并使用该值动态显示 HTML 内容

当我收到错误时无法读取未定义的属性“createComponent”,但如果我将其放在innerHTML 之外,它可以完美运行

我已经浏览了https://angular.io/guide/dynamic-component-loader但它对我的场景没有帮助

Sai*_*aif 5

您基本上要做的是在动态加载的字符串中动态加载一个组件。在 HTML 文件中内嵌或单独提供的任何模板都由 Angular 渲染引擎处理。(Ivy是从 Angular 9 开始的新的优化渲染引擎,从 Angular 4 到 8 调用默认渲染引擎Renderer2Renderer这是从 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();
  }
Run Code Online (Sandbox Code Playgroud)

这里错误的第一件事是,由于您定义的超时,显然ngAfterViewInit会在您setTimeout编写的方法之前调用,因此将始终未定义,因为它尚未实例化,我看到的下一个问题是关于您的模板, constructor 2000msmyRef

<div [innerHTML]="htmlChildString"></div>
Run Code Online (Sandbox Code Playgroud)

innerHTML 不应该包含任何角度符号,它只能包含可以直接嵌入到 DOM 中的纯 HTML,因为它们不会被 Angular 渲染引擎处理,这正是 @Andriy 已经指出的。

现在来解释为什么您尝试的操作是不可能的,假设您定义了一个名为AComponent具有模板的组件:

<span>I am {{name}}</span>
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

请注意以下行?

jit_elementDef2(0,null,null,1,'span',...),
Run Code Online (Sandbox Code Playgroud)

Angular 所做的是使用 JS 来创建元素,Angular 使用这个工厂来实例化 View Definition,而 View Definition 又用于创建组件 View。在引擎盖下,Angular 将应用程序表示为视图树。每个组件类型只有一个视图定义实例,用作所有视图的模板。但是对于每个组件实例,Angular 都会创建一个单独的视图。

上述工厂描述了组件视图的结构,并在实例化组件时使用。的jit_viewDef1是参照viewDef一个创建视图定义函数。视图定义接收视图定义节点作为参数,这些节点类似于 html 的结构,但也包含许多角度特定的细节。

该工厂是从resolveComponentFactory方法返回的内容,因为您已ChildViewComponent在模块中静态声明并将其标记为 an entryComponent,这就是 angular 决定预先生成其工厂的原因,即使它知道它当前未被 Router 或其他组件的模板使用.

同样ViewContainerRef是对视图容器(ng-templateng-container)的引用,它必须直接在模板中静态定义,或者应该可以通过ngSwitchngIf指令的组合遇到,但在任何情况下,编译器/渲染引擎都必须知道编译时事先存在此容器。

在您的情况下,当您通过 API 动态加载这些角度符号时,角度只知道这是一个简单的字符串,并且事先不知道这是可以在运行时包含视图的东西。如果你看过 Angular 生成的构建,它只包含 JS 文件,没有与你的组件对应的 HTML 模板或 CSS 样式文件。这些 HTML 文件中包含的每个模板都被转换为最终在运行时使用的工厂方法。

我会建议你的是,

  1. 要么仅通过其余 API 加载纯 HTML 内容,并将所有特定于角度的符号拉入您的Angular应用程序并有条件地加载它们,例如看看这个stackBlitz里面没有任何开箱即用的内容,它只是使用 CDK Portal,它是基本上只是方法resolveComponentFactorycreateComponent静态定义的抽象,templateRefviewContainerRef在单击按钮时动态加载。

在此处输入图片说明

  1. 或者创建另一个 JS 或 Angular 应用程序并ChildViewComponent在那里拉入并在需要时根据任何条件加载 Iframe 内容,
  2. 加载模板的编译版本,类似于 angular 延迟加载其模块的方式,以及一些如何以编程方式将其加载到路由器插座中。我不知道这是否可能,这只是我脑海中的一个想法,但我不希望这是直接的。