等效于Angular 2中的$ compile


134

我想手动编译一些包含指令的HTML。$compileAngular 2中的等效项是什么?

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

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

8
这些答案中的大多数(除了现在已弃用的答案中的1个)都不等于有角1 $编译。$ compile接受一个HTML字符串,并编译其中包含的组件和表达式。这些答案只是简单地动态创建预定义的组件(尚未实例化),并且不能采用字符串参数。这不是同一回事。有人知道这个问题的真正答案吗?
danday74 '17


角4想出了ComponentFactoryResolver这相当于角1.0。见$编译我的回答stackoverflow.com/questions/34784778/...
代码EZ

1
@ danday74-我同意这些答案均不能提供编译任意HTML模板的功能,而是仅从一组预先存在的组件中进行选择。我在这里找到了真正的答案,至少在Angular 8中有效:stackoverflow.com/questions/61137899/…。请参阅一个答案,该答案提供了一个可工作的StackBlitz,它可以编译运行时生成的任意HTML模板。
伊本

Answers:


132

Angular 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;
}

一个代码片段如何创建一个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);
            });
    });
}

一个代码片段如何使用以上结果

  // 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;
        //...
    });

完整说明以及所有详细信息,请阅读此处,或观察工作示例

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

要查看先前RC版本的先前解决方案,请在这篇文章的历史中进行搜索


2
非常感谢,我正在寻找一个可行的示例ComponentFactoryViewContainerRef替换现在不推荐使用的DynamicComponentLoader。
安德烈·洛克

1
@maxou这是index.html中的破折号引用,只需添加该引用即可,一切都会起作用
RadimKöhler16年

62
这真的很难吗?我曾经能够做这样的事情:$compile($element.contents())($scope.$new());现在它包含数百行代码,并完成了NgModule的创建……这是使我想摆脱NG2并继续前进到更好的东西的那种方式。
Karvapallo '16

2
JitCompiler如果您的示例可以与Compilerfrom 一起使用,则使用的好处是什么@angular/coreplnkr.co/edit/UxgkiT?p=preview
yurzui

4
哦,天哪,我只应该编写几行代码就可以编译一个小元素。我不太了解
Mr_Perfect

35

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


Angular2没有任何 $ compile等效项。您可以使用DynamicComoponentLoaderES6类并对其进行破解以动态地编译您的代码(请参阅此示例)):

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内核之前,它才起作用。


很棒的把戏!但是如果我的动态组件有一些输入,是否也可以绑定动态数据?
尤金·格卢霍托连科

2
回答我自己的问题:可以通过将数据传递给编译函数来实现。这里的小家伙plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko

此解决方案仅适用于beta-0。从beta 1到15,示例代码返回错误。错误:元素[object Object]中没有组件指令
Nicolas Forney

13
自rc1 DynamicComponentLoader被弃用以来
Benny Bottema '16

1
@BennyBottema既然DynamicComponentLoader已被弃用,我们如何在Angular 2中做同样的事情?说我有一个模式对话框,我想动态加载新组件作为其内容
Luke T O'Brien

16

我使用过的Angular版本-Angular 4.2.0

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

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

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

WidgetHost指令

 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() {

    }
}

瓷砖模型

 export interface TileModel {
      data: any;
    }

我博客的原始参考

官方文件

下载示例源代码


1
你忘了提entryComponents。没有它,您的解决方案将无法工作
yurzui

ComponentFactoryResolver位于angular2中。而且我认为这不等于$ compile
yurzui

@yurzui。但这满足了$ compile需要吗?
Code-EZ

@yurzui我曾经使用过$ compile的相同实现。当我们从模块中删除入口组件时,将引发错误ImageTextWidgetComponent未加载。但是应用程序仍然有效
Code-EZ

1
@BecarioSenior如果您不转换为任何模型类,则默认为动态。在此示例中,属性数据的类型为any,这意味着您可以将任何数据作为输入传递给动态组件。它使您的代码更具可读性。
Code-EZ


3

为了动态地创建组件实例并将其附加到您的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);
  }

}

1
该DynamicComponentLoader不再是:“(这已被废弃后,有ComponentResolver现在存在ComponentFactoryResolver(blog.rangle.io/dynamically-creating-components-with-angular-2
11MB

3

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>

1
“ Angular TypeScript”标题的含义并不明显。该解决方案对ES5和ES6无效吗?提供此程序包的程序化使用示例(与相对应)的示例将很有帮助$compile(...)($scope)。即使在回购自述文件中也没有任何内容。
Estus Flask,


0

我知道这个问题已经很久了,但是我花了数周的时间试图弄清楚如何在启用AOT的情况下进行此工作。我能够编译一个对象,但无法执行现有组件。好吧,我最终决定更改策略,因为我不希望编译代码太多,而是执行自定义模板。我的想法是添加任何人都可以做并循环通过现有工厂的html。这样,我可以搜索element / attribute / etc。在该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
            });
        });
    }
}

-5

如果要注入html代码,请使用指令

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

如果要在某个位置加载整个组件,请使用DynamicComponentLoader:

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


2
我想将HTML片段作为字符串注入并将其传递给组件编译器,然后将该组件附加到我的DOM中。您能否举一个例子说明您的两种解决方案如何工作?
pixelbits

3
使用innerHtml不会在htmlVar中编译任何组件–Juanín
2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.