【问题标题】:Compile dynamic HTML in Angular 4/5- something similar to $compile in Angular JS在 Angular 4/5 中编译动态 HTML - 类似于 Angular JS 中的 $compile
【发布时间】:2020-04-17 07:16:36
【问题描述】:

我想通过对服务器的服务调用接收 HTML 数据(这是肯定的。我不能将模板保存在本地)并在内部操作它们如何显示它(作为模式或整页)。这个带有 Angular 标签的 HTML 应该循环到一个组件并一起工作。 Angular JS 中的 $compile 最多。

我正在使用 Angular 5 开发解决方案,应该与 AOT 编译器兼容。我已经参考了几个解决方案,并对已弃用和更新的解决方案感到困惑。请帮我。我相信您更新的答案也会对其他人有所帮助.. 非常感谢您!

【问题讨论】:

  • 您提到了您已经研究过的几个解决方案,如果您描述一些,以及您尝试过的内容以及您对什么感到困惑,那就太好了。问题的标题很好,值得 SEO,但内容略低于标准。

标签: angular compilation angular5


【解决方案1】:

要即时渲染 HTML,您需要 DomSanitizer。例如。像这样:

<!-- template -->
<div [innerHTML]="htmlData"></div>

// component
import { Component } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  htmlData: any;
  constructor(private sanitizer: DomSanitizer) {}

  ngOnInit() {
    this.htmlData= this.sanitizer.bypassSecurityTrustHtml('<div style="border: 1px solid red;"><h2>Safe Html</h2><span class="user-content">Server prepared this html block.</span></div>');
  }
}

现在,这就是它的要点。你显然还需要一个加载机制。您可能还想在此块中包含一些数据 - 如果它是简单数据,它可以在运行中:

this.htmlData = this.sanitizer.bypassSecurityTrustHtml(`<div>${this.someValue}</div>`);

对于更复杂的场景,您可能需要创建一个动态组件。

编辑:动态解析的组件示例。这样,您就可以从服务器发送的 html 即时创建组件。

@Component({
  selector: 'my-component',
  template: `<h2>Stuff bellow will get dynamically created and injected<h2>
          <div #vc></div>`
})
export class TaggedDescComponent {
  @ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;

  private cmpRef: ComponentRef<any>;

  constructor(private compiler: Compiler,
              private injector: Injector,
              private moduleRef: NgModuleRef<any>,
              private backendService: backendService,
              ) {}

  ngAfterViewInit() {
    // Here, get your HTML from backend.
    this.backendService.getHTMLFromServer()
        .subscribe(rawHTML => this.createComponentFromRaw(rawHTML));
  }

  // Here we create the component.
  private createComponentFromRaw(template: string) {
    // Let's say your template looks like `<h2><some-component [data]="data"></some-component>`
    // As you see, it has an (existing) angular component `some-component` and it injects it [data]

    // Now we create a new component. It has that template, and we can even give it data.
    const tmpCmp = Component({ template, styles })(class {
      // the class is anonymous. But it's a quite regular angular class. You could add @Inputs,
      // @Outputs, inject stuff etc.
      data: { some: 'data'};
      ngOnInit() { /* do stuff here in the dynamic component */}
    });

    // Now, also create a dynamic module.
    const tmpModule = NgModule({
      imports: [RouterModule],
      declarations: [tmpCmp],
      // providers: [] - e.g. if your dynamic component needs any service, provide it here.
    })(class {});

    // Now compile this module and component, and inject it into that #vc in your current component template.
    this.compiler.compileModuleAndAllComponentsAsync(tmpModule)
      .then((factories) => {
        const f = factories.componentFactories[0];
        this.cmpRef = f.create(this.injector, [], null, this.moduleRef);
        this.cmpRef.instance.name = 'my-dynamic-component';
        this.vc.insert(this.cmpRef.hostView);
      });
  }

  // Cleanup properly. You can add more cleanup-related stuff here.
  ngOnDestroy() {
    if(this.cmpRef) {
      this.cmpRef.destroy();
    }
  }
}

【讨论】:

  • 感谢您的回复。我已经尝试过这种只呈现 HTML 视图的方法。我希望模板中的 Angular 标签能够正常工作。
  • 为了让标签也能正常工作,您可以动态引导组件。
  • 简直太棒了。这是一个直截了当的答案,对我有用!非常感谢:)
  • 在 Angular 5 中用于简单的 html,就像在你的第一个示例中那样 bypassSecurityTrustHtml 是多余的
  • @Zlatko 您的组件方法不适用于 --prod build。出现“未加载运行时编译器”错误。
【解决方案2】:

这是一个使用eval 的动态模板动态组件类代码的扩展解决方案。请参阅下面的不带 eval 的变体。

Stackblitz Example 不使用 eval。

import { Component, ViewChild, ViewContainerRef, NgModule, Compiler, Injector, NgModuleRef } from '@angular/core';
import {CommonModule} from "@angular/common";
import { RouterModule } from "@angular/router"

@Component({
    selector: 'app-root',
    template: `<div style="text-align:center">
    <h1>
    Welcome to {{ title }}!
    </h1>
</div>
<div #content></div>`
})
export class AppComponent {

    title = 'Angular';

    @ViewChild("content", { read: ViewContainerRef })
    content: ViewContainerRef;

    constructor(private compiler: Compiler,
    private injector: Injector,
    private moduleRef: NgModuleRef<any>, ) {
    }

    // Here we create the component.
    private createComponentFromRaw(klass: string, template: string, styles = null) {
    // Let's say your template looks like `<h2><some-component [data]="data"></some-component>`
    // As you see, it has an (existing) angular component `some-component` and it injects it [data]

    // Now we create a new component. It has that template, and we can even give it data.
    var c = null;
    eval(`c = ${klass}`);
    let tmpCmp = Component({ template, styles })(c);

    // Now, also create a dynamic module.
    const tmpModule = NgModule({
        imports: [CommonModule, RouterModule],
        declarations: [tmpCmp],
        // providers: [] - e.g. if your dynamic component needs any service, provide it here.
    })(class { });

    // Now compile this module and component, and inject it into that #vc in your current component template.
    this.compiler.compileModuleAndAllComponentsAsync(tmpModule)
        .then((factories) => {
        const f = factories.componentFactories[factories.componentFactories.length - 1];
        var cmpRef = f.create(this.injector, [], undefined, this.moduleRef);
        cmpRef.instance.name = 'app-dynamic';
        this.content.insert(cmpRef.hostView);
        });
    }

    ngAfterViewInit() {
    this.createComponentFromRaw(`class _ {
        constructor(){
        this.data = {some: 'data'};
        }
        ngOnInit() { }
        ngAfterViewInit(){}
        clickMe(){ alert("Hello eval");}
        }`, `<button (click)="clickMe()">Click Me</button>`)
    }
}

这是一个没有动态类代码的小变化。通过类绑定动态模板变量没有成功,而是使用了一个函数:

function A() {
    this.data = { some: 'data' };
    this.clickMe = () => { alert("Hello tmpCmp2"); }
}
let tmpCmp2 = Component({ template, styles })(new A().constructor);

【讨论】:

  • 感谢您的回答!基于此,我能够得到一个工作示例。但是,模板内的动态组件和嵌套组件中的导入对我不起作用。 stackblitz.com/edit/angular-1womya
  • 感谢您优化答案。更新 stackblitz.com/edit/angular-cnnpxh 示例并将 HelloComponent 添加到动态组件。
  • 我很好奇您为什么使用模板文字而不是工厂来创建该类?至少在这个例子中,它看起来完全没有必要。
  • 将 CommonModule 添加到导入模块请阅读stackblitz.com/edit/dynamic-raw-template
  • 这是迄今为止我找到的最佳解决方案。简洁明了。
【解决方案3】:

查看 npm 包Ngx-Dynamic-Compiler

这个包使您能够使用诸如 *ngIf,*ngFor 之类的角度指令,使用字符串插值进行数据绑定,并在运行时创建一个真正动态的组件。已提供 AOT 支持。

【讨论】:

    【解决方案4】:

    查看此包一https://www.npmjs.com/package/@codehint-ng/html-compiler 或查看源代码以了解如何将带有数据和事件的配对 JS 对象编译 HTML 字符串。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-21
      • 1970-01-01
      • 1970-01-01
      • 2019-06-20
      • 2019-07-14
      • 2016-12-20
      • 2018-01-05
      相关资源
      最近更新 更多