【问题标题】:Equivalent of $compile in Angular 2Angular 2 中的 $compile 等价物
【发布时间】:2016-04-19 11:52:56
【问题描述】:

我想手动编译一些包含指令的 HTML。 Angular 2 中 $compile 的等价物是什么?

例如,在 Angular 1 中,我可以动态编译 HTML 片段并将其附加到 DOM:

var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);

【问题讨论】:

  • 大多数这些答案(除了 1 现在已弃用的答案)不等同于 angular 1 $compile。 $compile 接受一个 HTML 字符串并编译其中包含的组件和表达式。这些答案只是动态地创建预定义的组件(尚未实例化)并且不能采用字符串参数。这不是一回事。有人知道这个问题的真正答案吗?
  • Angular 4 提出了 ComponentFactoryResolver,它相当于 $ compile in Angular 1.0 。见我的回答stackoverflow.com/questions/34784778/…
  • @danday74 - 我同意这些答案都没有提供编译任意 HTML 模板的能力,而是从一组预先存在的组件中进行选择。我在这里找到了真正的答案,它至少适用于 Angular 8:stackoverflow.com/questions/61137899/…。请参阅一个答案,它提供了一个有效的 StackBlitz,它可以编译任意运行时生成的 HTML 模板。

标签: angular typescript angular2-compiler version-compatibility


【解决方案1】:

你可以看到组件,它允许编译简单的动态Angular组件https://www.npmjs.com/package/@codehint-ng/html-compiler

【讨论】:

  • 它使用这个库工作! :) 感谢您的推荐。
【解决方案2】:

我知道这个问题已经过时了,但我花了数周时间试图弄清楚如何在启用 AOT 的情况下解决这个问题。我能够编译一个对象,但永远无法执行现有的组件。好吧,我最终决定改变策略,因为我并不想编译代码,而是执行自定义模板。我的想法是添加任何人都可以做的 html 并循环通过现有的工厂。这样做我可以搜索元素/属性/等。命名并在该 HTMLElement 上执行组件。我能够让它工作,并认为我应该分享它以节省其他人我浪费在它上的大量时间。

@Component({
    selector: "compile",
    template: "",
    inputs: ["html"]
})
export class CompileHtmlComponent implements OnDestroy {
    constructor(
        private content: ViewContainerRef,
        private injector: Injector,
        private ngModRef: NgModuleRef<any>
    ) { }

    ngOnDestroy() {
        this.DestroyComponents();
    }

    private _ComponentRefCollection: any[] = null;
    private _Html: string;

    get Html(): string {
        return this._Html;
    }
    @Input("html") set Html(val: string) {
        // recompile when the html value is set
        this._Html = (val || "") + "";
        this.TemplateHTMLCompile(this._Html);
    }

    private DestroyComponents() { // we need to remove the components we compiled
        if (this._ComponentRefCollection) {
            this._ComponentRefCollection.forEach((c) => {
                c.destroy();
            });
        }
        this._ComponentRefCollection = new Array();
    }

    private TemplateHTMLCompile(html) {
        this.DestroyComponents();
        this.content.element.nativeElement.innerHTML = html;
        var ref = this.content.element.nativeElement;
        var factories = (this.ngModRef.componentFactoryResolver as any)._factories;
        // here we loop though the factories, find the element based on the selector
        factories.forEach((comp: ComponentFactory<unknown>) => {
            var list = ref.querySelectorAll(comp.selector);
            list.forEach((item) => {
                var parent = item.parentNode;
                var next = item.nextSibling;
                var ngContentNodes: any[][] = new Array(); // this is for the viewchild/viewchildren of this object

                comp.ngContentSelectors.forEach((sel) => {
                    var ngContentList: any[] = new Array();

                    if (sel == "*") // all children;
                    {
                        item.childNodes.forEach((c) => {
                            ngContentList.push(c);
                        });
                    }
                    else {
                        var selList = item.querySelectorAll(sel);

                        selList.forEach((l) => {
                            ngContentList.push(l);
                        });
                    }

                    ngContentNodes.push(ngContentList);
                });
                // here is where we compile the factory based on the node we have
                let component = comp.create(this.injector, ngContentNodes, item, this.ngModRef);

                this._ComponentRefCollection.push(component); // save for our destroy call
                // we need to move the newly compiled element, as it was appended to this components html
                if (next) parent.insertBefore(component.location.nativeElement, next);
                else parent.appendChild(component.location.nativeElement);

                component.hostView.detectChanges(); // tell the component to detectchanges
            });
        });
    }
}

【讨论】:

    【解决方案3】:

    2.3.0 (2016-12-07)

    获取所有详细信息检查:

    查看实际效果:

    校长:

    1) 创建模板
    2) 创建组件
    3) 创建模块
    4) 编译模块
    5) 创建(并缓存)ComponentFactory
    6) 使用 Target 创建它的实例

    快速概览如何创建组件

    createNewComponent (tmpl:string) {
      @Component({
          selector: 'dynamic-component',
          template: tmpl,
      })
      class CustomDynamicComponent  implements IHaveDynamicData {
          @Input()  public entity: any;
      };
      // a component for this particular template
      return CustomDynamicComponent;
    }
    

    如何将组件注入到 NgModule 中

    createComponentModule (componentType: any) {
      @NgModule({
        imports: [
          PartsModule, // there are 'text-editor', 'string-editor'...
        ],
        declarations: [
          componentType
        ],
      })
      class RuntimeComponentModule
      {
      }
      // a module for just this Type
      return RuntimeComponentModule;
    }
    

    一个代码sn-p如何创建ComponentFactory(并缓存它)

    public createComponentFactory(template: string)
        : Promise<ComponentFactory<IHaveDynamicData>> {    
        let factory = this._cacheOfFactories[template];
    
        if (factory) {
            console.log("Module and Type are returned from cache")
    
            return new Promise((resolve) => {
                resolve(factory);
            });
        }
    
        // unknown template ... let's create a Type for it
        let type   = this.createNewComponent(template);
        let module = this.createComponentModule(type);
    
        return new Promise((resolve) => {
            this.compiler
                .compileModuleAndAllComponentsAsync(module)
                .then((moduleWithFactories) =>
                {
                    factory = _.find(moduleWithFactories.componentFactories
                                    , { componentType: type });
    
                    this._cacheOfFactories[template] = factory;
    
                    resolve(factory);
                });
        });
    }
    

    一个代码sn -p如何使用上面的结果

      // here we get Factory (just compiled or from cache)
      this.typeBuilder
          .createComponentFactory(template)
          .then((factory: ComponentFactory<IHaveDynamicData>) =>
        {
            // Target will instantiate and inject component (we'll keep reference to it)
            this.componentRef = this
                .dynamicComponentTarget
                .createComponent(factory);
    
            // let's inject @Inputs to component instance
            let component = this.componentRef.instance;
    
            component.entity = this.entity;
            //...
        });
    

    带有所有细节的完整描述read here,或观察working example

    .

    .

    已过时 - Angular 2.0 RC5 相关(仅限 RC5)

    要查看以前 RC 版本的解决方案,请搜索the history of this post

    【讨论】:

    • 非常感谢,我正在寻找 ComponentFactoryViewContainerRef 的工作示例来替换现在已弃用的 DynamicComponentLoader。
    • @maxou 这是 index.html 中的 lo-dash 引用,只需添加该引用即可
    • 真的有这么难吗?我以前只能做这样的事情:$compile($element.contents())($scope.$new());,现在它是数百行代码,完成了 NgModule 的创建......这就是让我想要避开 NG2 并继续前进的事情更好的东西。
    • 如果您的示例可以使用来自@angular/coreCompiler,那么使用JitCompiler 有什么好处? plnkr.co/edit/UxgkiT?p=preview
    • 天哪,我应该写多少行代码来编译一个小元素。没听懂
    【解决方案4】:

    我使用过的 Angular 版本 - Angular 4.2.0

    Angular 4 提出了 ComponentFactoryResolver 来在运行时加载组件。这是 Angular 1.0 中 $compile 的一种相同实现,可以满足您的需求

    在下面的示例中,我将 ImageWidget 组件动态加载到 DashboardTileComponent

    为了加载一个组件,您需要一个可以应用于 ng-template 的指令,这将有助于放置动态组件

    WidgetHostDirective

     import { Directive, ViewContainerRef } from '@angular/core';
    
        @Directive({
          selector: '[widget-host]',
        })
        export class DashboardTileWidgetHostDirective {
          constructor(public viewContainerRef: ViewContainerRef) { 
    
    
          }
        }
    

    该指令注入 ViewContainerRef 以访问将托管动态添加的组件的元素的视图容器。

    DashboardTileComponent(占位符组件渲染动态组件)

    此组件接受来自父组件的输入,或者您可以根据您的实现从您的服务中加载。该组件在运行时解决组件的主要作用。在这个方法中,您还可以看到一个名为 renderComponent() 的方法,它最终从服务中加载组件名称并使用 ComponentFactoryResolver 进行解析,最后将数据设置为动态组件。

    import { Component, Input, OnInit, AfterViewInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core';
    import { DashboardTileWidgetHostDirective } from './DashbardWidgetHost.Directive';
    import { TileModel } from './Tile.Model';
    import { WidgetComponentService } from "./WidgetComponent.Service";
    
    
    @Component({
        selector: 'dashboard-tile',
        templateUrl: 'app/tile/DashboardTile.Template.html'
    })
    
    export class DashboardTileComponent implements OnInit {
        @Input() tile: any;
        @ViewChild(DashboardTileWidgetHostDirective) widgetHost: DashboardTileWidgetHostDirective;
        constructor(private _componentFactoryResolver: ComponentFactoryResolver,private widgetComponentService:WidgetComponentService) {
    
        }
    
        ngOnInit() {
    
        }
        ngAfterViewInit() {
            this.renderComponents();
        }
        renderComponents() {
            let component=this.widgetComponentService.getComponent(this.tile.componentName);
            let componentFactory = this._componentFactoryResolver.resolveComponentFactory(component);
            let viewContainerRef = this.widgetHost.viewContainerRef;
            let componentRef = viewContainerRef.createComponent(componentFactory);
            (<TileModel>componentRef.instance).data = this.tile;
    
        }
    }
    

    DashboardTileComponent.html

     <div class="col-md-2 col-lg-2 col-sm-2 col-default-margin col-default">        
                            <ng-template widget-host></ng-template>
    
              </div>
    

    WidgetComponentService

    这是一个服务工厂,用于注册您要动态解析的所有组件

    import { Injectable }           from '@angular/core';
    import { ImageTextWidgetComponent } from "../templates/ImageTextWidget.Component";
    @Injectable()
    export class WidgetComponentService {
      getComponent(componentName:string) {
              if(componentName==="ImageTextWidgetComponent"){
                  return ImageTextWidgetComponent
              }
      }
    }
    

    ImageTextWidgetComponent(我们在运行时加载的组件)

    import { Component, OnInit, Input } from '@angular/core';
    
    
    @Component({
        selector: 'dashboard-imagetextwidget',
        templateUrl: 'app/templates/ImageTextWidget.html'
    })
    
    export class ImageTextWidgetComponent implements OnInit {
         @Input() data: any;
        constructor() { }
    
        ngOnInit() { }
    }
    

    添加 最后将此 ImageTextWidgetComponent 作为 entryComponent 添加到您的应用模块中

    @NgModule({
        imports: [BrowserModule],
        providers: [WidgetComponentService],
        declarations: [
            MainApplicationComponent,
            DashboardHostComponent,
            DashboardGroupComponent,
            DashboardTileComponent,
            DashboardTileWidgetHostDirective,
            ImageTextWidgetComponent
            ],
        exports: [],
        entryComponents: [ImageTextWidgetComponent],
        bootstrap: [MainApplicationComponent]
    })
    export class DashboardModule {
        constructor() {
    
        }
    }
    

    TileModel

     export interface TileModel {
          data: any;
        }
    

    Orginal Reference from my blog

    Official Documentation

    Download Sample Source Code

    【讨论】:

    • 你忘了提到entryComponents。没有它,您的解决方案将无法工作
    • ComponentFactoryResolver 在 angular2 中。而且我认为它不等同于 $compile
    • @yurzui 。但它满足了 $compile 的需要吗??
    • @yurzui 我使用了与 $compile 相同的实现。当我们从模块中移除入口组件时,它会抛出一个错误 ImageTextWidgetComponent is not loaded。但应用程序仍然有效
    • @BecarioSenior 如果您没有被强制转换为任何模型类,它将是默认动态的。在此示例中,属性数据的类型是 any ,这意味着您可以将任何数据作为输入传递给动态组件。它使您的代码更具可读性。
    【解决方案5】:

    这个 npm 包让我更容易: https://www.npmjs.com/package/ngx-dynamic-template

    用法:

    <ng-template dynamic-template
                 [template]="'some value:{{param1}}, and some component <lazy-component></lazy-component>'"
                 [context]="{param1:'value1'}"
                 [extraModules]="[someDynamicModule]"></ng-template>
    

    【讨论】:

      【解决方案6】:

      Angular TypeScript/ES6 (Angular 2+)

      同时使用 AOT + JIT。

      我在这里创建了如何使用它: https://github.com/patrikx3/angular-compile

      npm install p3x-angular-compile
      

      组件:应该有一个上下文和一些 html 数据...

      HTML:

      <div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>
      

      【讨论】:

      • “Angular TypeScript”标题的含义并不明显。该解决方案对 ES5 和 ES6 无用吗?提供这个包的编程使用示例会很有帮助,它与$compile(...)($scope) 直接对应。即使在 repo 自述文件中也没有任何内容。
      【解决方案7】:

      注意:正如@BennyBottema 在评论中提到的那样,DynamicComponentLoader 现在已被弃用,因此这个答案也是如此。


      Angular2 没有任何 $compile 等价物。您可以使用 DynamicComoponentLoader 并使用 ES6 类来动态编译您的代码(参见 plunk):

      import {Component, DynamicComponentLoader, ElementRef, OnInit} from 'angular2/core'
      
      function compileToComponent(template, directives) {
        @Component({ 
          selector: 'fake', 
          template , directives
        })
        class FakeComponent {};
        return FakeComponent;
      }
      
      @Component({
        selector: 'hello',
        template: '<h1>Hello, Angular!</h1>'
      })
      class Hello {}
      
      @Component({
        selector: 'my-app',
        template: '<div #container></div>',
      })
      export class App implements OnInit {
        constructor(
          private loader: DynamicComponentLoader, 
          private elementRef: ElementRef,
        ) {}
      
        ngOnInit() {} {
          const someDynamicHtml = `<hello></hello><h2>${Date.now()}</h2>`;
      
          this.loader.loadIntoLocation(
            compileToComponent(someDynamicHtml, [Hello])
            this.elementRef,
            'container'
          );
        }
      }
      

      但它只有在 html 解析器位于 angular2 核心内时才会起作用。

      【讨论】:

      • 太棒了!但是如果我的动态组件有一些输入,是否也可以绑定动态数据?
      • 回答我自己的问题:可以通过将数据传递给编译函数。这里是笨拙的plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
      • 此解决方案仅适用于 beta-0。从 beta 1 到 15,示例代码返回错误。错误:元素 [object Object] 处没有组件指令
      • 由于 rc1 DynamicComponentLoader 已被弃用
      • @BennyBottema 因为 DynamicComponentLoader 已被弃用,我们如何在 Angular 2 中做同样的事情?假设我有一个模态对话框,我想动态加载一个新组件,因为它的内容
      【解决方案8】:

      为了动态创建组件实例并将其附加到您的 DOM,您可以使用以下脚本,并且应该在 Angular RC 中工作:

      html模板:

      <div>
        <div id="container"></div>
        <button (click)="viewMeteo()">Meteo</button>
        <button (click)="viewStats()">Stats</button>
      </div>
      

      加载器组件

      import { Component, DynamicComponentLoader, ElementRef, Injector } from '@angular/core';
      import { WidgetMeteoComponent } from './widget-meteo';
      import { WidgetStatComponent } from './widget-stat';
      
      @Component({
        moduleId: module.id,
        selector: 'widget-loader',
        templateUrl: 'widget-loader.html',
      })
      export class WidgetLoaderComponent  {
      
        constructor( elementRef: ElementRef,
                     public dcl:DynamicComponentLoader,
                     public injector: Injector) { }
      
        viewMeteo() {
          this.dcl.loadAsRoot(WidgetMeteoComponent, '#container', this.injector);
        }
      
        viewStats() {
          this.dcl.loadAsRoot(WidgetStatComponent, '#container', this.injector);
        }
      
      }
      

      【讨论】:

      【解决方案9】:

      如果你想注入 html 代码使用指令

      <div [innerHtml]="htmlVar"></div>
      

      如果你想在某个地方加载整个组件,请使用 DynamicComponentLoader:

      https://angular.io/docs/ts/latest/api/core/DynamicComponentLoader-class.html

      【讨论】:

      • 我想将 HTML 片段作为字符串注入并将其传递给组件编译器,然后将该组件附加到我的 DOM 中。您能否举例说明您的任一解决方案如何工作?
      • 使用 innerHtml 不会编译 htmlVar 中的任何组件
      猜你喜欢
      • 2017-01-19
      • 2017-02-20
      • 2016-11-02
      • 2016-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-17
      相关资源
      最近更新 更多