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-templateng-container)的引用,它必须直接在模板中静态定义,或者应该可以通过ngSwitchngIf 指令的组合,但在任何情况下,编译器/渲染引擎在编译时都必须事先知道此容器的存在。

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

我推荐你的是,

  1. 要么仅加载纯粹的 HTML 内容而不是其余内容 API 并将所有 angular 特定符号拉入您的 angular 应用程序并有条件地加载它们, 例如看看这个 stackBlitz 里面没有开箱即用的东西,它只是使用 CDK Portal,它基本上只是方法 resolveComponentFactorycreateComponent 的抽象以及静态定义的templateRef 单击按钮时动态加载到 viewContainerRef

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