【问题标题】:How to use ng-template inside innerHTML in Angular如何在Angular的innerHTML中使用ng-template
【发布时间】:2020-01-20 14:34:12
【问题描述】:

谁能帮帮我,如何在innerHTML标签中使用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 内容

当我收到错误 Cannot read property 'createComponent' of undefined 但如果我把它放在 innerHTML 之外它可以完美地工作

我已经通过https://angular.io/guide/dynamic-component-loader 但这对我的情景没有帮助

【问题讨论】:

  • 你有笔可以看看吗?究竟显示了什么,或者您是否收到模板错误?
  • @SamuelMiddendorp,我没有笔,但我已经更新了问题

标签: angular angular2-template innerhtml ng-template angular-dynamic-components


【解决方案1】:

您基本上尝试做的是在动态加载的字符串中动态加载组件。 Angular 渲染引擎处理内联或单独在 HTML 文件中提供的任何模板。 (Ivy 是从 Angular 9 开始的新优化渲染引擎,从 Angular 4 到 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 不应该包含任何角度符号,它只能包含可以直接嵌入到 DOM 中的纯 HTML,因为它们不会被 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 使用这个工厂来实例化视图定义,而视图定义又用于创建组件视图。在引擎盖下,Angular 将应用程序表示为视图树。每个组件类型只有一个视图定义实例,它充当所有视图的模板。但是对于每个组件实例,Angular 都会创建一个单独的视图。

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

这个工厂是从 resolveComponentFactory 方法返回的,因为你已经在你的模块中静态声明了 ChildViewComponent 并将它标记为 entryComponent,这就是 angular 决定预先生成它的工厂的原因,即使它知道它当前没有被路由器或其他组件的模板使用。

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

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

我建议你这样做,

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

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

【讨论】:

    【解决方案2】:

    您可以使用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) {}
    }
    

    堆栈闪电战:https://stackblitz.com/edit/angular-t9roxg?file=src%2Fapp%2Fhello.component.ts

    请记住,通过这种方式,HTML 是作为纯 HTML 传递的,无需任何 Angular 处理

    【讨论】:

    • 感谢您的回答,我忘了提到我在 ng-template 中使用了子组件,我已经更新了问题,你能帮忙解决一下,出了什么问题
    • 尝试在 stackblitz.com/angular/kbayapnxprb 关注 Angular 原始演示。今晚晚些时候我会看看它
    • 谢谢@Andriy 我已经试过了,问题是在上面的教程中没有像我加载的方式加载孩子
    • 所以,请尝试分享您的 stackblitz 示例,以便我观察并尝试提供帮助
    • 好的,我现在就创建它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-21
    • 2021-07-25
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 2020-11-28
    相关资源
    最近更新 更多