Compile dynamic HTML in Angular 4/5- something similar to $compile in Angular JS

前端 未结 4 1348
夕颜
夕颜 2020-11-27 16:54

I wanted to receive an HTML data via service call to server(this is for sure. I cannot keep templates in local) and manipulate them internally on how to show it(either as a

相关标签:
4条回答
  • 2020-11-27 17:16

    For rendering HTML on the fly, you need DomSanitizer. E.g. something like this:

    <!-- 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>');
      }
    }
    

    Now, that's the gist of it. You obviously also need a loading mechanic. You might also want to include some data into this block - if it's simple data, it can be on the fly:

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

    For more complex scenarios you might need to create a dynamic component.

    Edit: an example of a component resolved dynamically. With this, you create a component on-the-fly from server-sent 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();
        }
      }
    }
    
    0 讨论(0)
  • 2020-11-27 17:26

    Here's an extended solution with dynamic template and dynamic component class code using eval. See below for a variaton without eval.

    Stackblitz Example without using 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>`)
        }
    }
    

    Here's a small variation without dynamic class code. Binding the dynamic template variables via class did not work out, instead a function was used:

    function A() {
        this.data = { some: 'data' };
        this.clickMe = () => { alert("Hello tmpCmp2"); }
    }
    let tmpCmp2 = Component({ template, styles })(new A().constructor);
    
    0 讨论(0)
  • 2020-11-27 17:27

    Check this package one https://www.npmjs.com/package/@codehint-ng/html-compiler Or see the source code to see how to compile the HTML string with paired JS object with data and events.

    0 讨论(0)
  • 2020-11-27 17:30

    Checkout the npm package Ngx-Dynamic-Compiler

    This package enables you to use angular directives like *ngIf,*ngFor , Databinding using string interpolation and creates a truly dynamic component at runtime. AOT support has been provided.

    0 讨论(0)
提交回复
热议问题