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つは現在非推奨の回答を除く)は、angular 1 $ compileと同等ではありません。$ compileはHTML文字列を受け取り、そこに含まれるコンポーネントと式をコンパイルします。これらの回答は、事前定義されたコンポーネント(まだインスタンス化されていない)を動的に作成するだけで、文字列引数をとることはできません。これは同じことではありません。誰かがこの質問への本当の答えを知っていますか?
danday74 2017


Angular 4は、Angular 1.0での$コンパイルに相当するComponentFactoryResolverを考案しました。私の答えを参照してください。stackoverflow.com
Code-EZ

1
@ danday74-これらの回答のいずれもが任意のHTMLテンプレートをコンパイルする機能を提供しないことに同意します。代わりに、既存のコンポーネントのセットから選択するだけです。私はここで本当の答えを見つけました。これは少なくともAngular 8で機能します:stackoverflow.com/questions/61137899/…。ランタイムで生成された任意のHTMLテンプレートをコンパイルする実用的なStackBlitzを提供する1つの答えを参照してください。
EbenH

回答:


132

Angular 2.3.0(2016-12-07)

すべての詳細チェックを取得するには:

実際の動作を確認するには:

プリンシパル:

1)テンプレートの作成
2)コンポーネントの作成
3)モジュールの作成
4)モジュールのコンパイル
5)ComponentFactoryの作成(およびキャッシュ)
6)ターゲットを使用してそのインスタンスを作成

コンポーネントを作成する方法の概要

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

ここですべての詳細を含む完全な説明を読むか、実際の例を観察してください

OBSOLETE-Angular 2.0 RC5関連(RC5のみ)

以前のRCバージョンの以前のソリューションを確認するには、この投稿の履歴を検索してください


2
どうもありがとうございました。私は、現在非推奨のDynamicComponentLoaderの実用的な例を探し、ComponentFactoryそれViewContainerRefを置き換えるために探していました。
Andre Loker、2016年

1
@maxouこれは、index.htmlのローダッシュ参照です。その参照を追加するだけですべてが機能します
RadimKöhlerOct

62
これは本当に難しいですか?以前は次のようなことができるようになりました:$compile($element.contents())($scope.$new());今では数百行のコードで、NgModuleの作成が完了しています...これは、NG2を避けてより良いものに移行したいと思っている種類のことです。
Karvapallo 16

2
JitCompilerあなたの例がCompilerfromで動作する可能性がある場合に使用する利点は何@angular/coreですか?plnkr.co/edit/UxgkiT?p=preview
yurzui

4
なんてこった、小さな要素をコンパイルするためだけに何行のコードを書くべきか。よくわかりませんでした
Mr_Perfect

35

注: @BennyBottemaがコメントで言及しているように、DynamicComponentLoaderは非推奨になりました。したがって、この回答も同様です。


Angular2には同等の$ compileはありません 。DynamicComoponentLoaderES6クラスを使用してハックすると、コードを動的にコンパイルできます(この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コア内にあるまでのみ機能します。


素晴らしいトリック!しかし、私の動的コンポーネントに入力がある場合、動的データもバインドできますか?
Eugene Gluhotorenko 2016年

2
私自身の質問への回答:コンパイル関数にデータを渡すことで可能です。ここで、plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko 2016年

このソリューションはベータ0でのみ機能します。ベータ1から15までのサンプルコードはエラーを返します。エラー:要素[オブジェクトオブジェクト]にコンポーネントディレクティブがありません
Nicolas Forney '25

13
rc1 DynamicComponentLoaderが非推奨となったため
Benny Bottema '27年

1
@BennyBottema DynamicComponentLoaderは廃止されたので、Angular 2で同じようなことをするにはどうすればよいですか?モーダルダイアログがあり、新しいコンポーネントをコンテンツとして動的にロードしたいとします
Luke T O'Brien

16

私が使用した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(動的コンポーネントをレンダリングするためのプレースホルダーコンポーネント)

このコンポーネントは、親コンポーネントからの入力を受け入れるか、実装に基づいてサービスからロードできます。このコンポーネントは、実行時にコンポーネントを解決するために主要な役割を果たしています。このメソッドでは、最終的にサービスからコンポーネント名をロードしてComponentFactoryResolverで解決し、最後にデータを動的コンポーネントに設定するrenderComponent()という名前のメソッドも確認できます。

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

私のブログからの元の参照

公式ドキュメント

サンプルソースコードをダウンロード


1
について言及するのを忘れていましたentryComponents。それがなければあなたの解決策は機能しません
yurzui

ComponentFactoryResolverはangular2でした。そして、それは$ compileと同等ではないと思います
yurzui

@yurzui。しかし、それは$ compileの必要性を正しく満たしていますか?
Code-EZ

@yurzui $ compileを使用して同じ種類の実装を使用しました。モジュールからエントリコンポーネントを削除すると、ImageTextWidgetComponentが読み込まれていないというエラーがスローされます。しかし、アプリケーションは引き続き機能します
Code-EZ

1
@BecarioSeniorは、どのモデルクラスにもキャストされない場合、デフォルトで動的になります。この例では、プロパティデータのタイプはanyです。つまり、任意のデータを入力として動的コンポーネントに渡すことができます。コードが読みやすくなります。
Code-EZ

9

この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>

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)です。レポのreadmeにも何もありません。
Estus Flask 2017


0

私はこの問題が古いことを知っていますが、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
            });
        });
    }
}

-5

HTMLコードを挿入する場合はディレクティブを使用します

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

コンポーネント全体をどこかにロードする場合は、DynamicComponentLoaderを使用します。

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


2
HTMLのフラグメントを文字列として挿入してコンポーネントコンパイラに渡し、そのコンポーネントをDOMに追加したいと考えています。どちらのソリューションが機能するかの例を挙げていただけますか?
pixelbits 2016年

3
innerHTMLのを使用してhtmlVar内部の任意のコンポーネントをコンパイルしません
Juanín
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.