Setara dengan $ kompilasi dalam Angular 2

134

Saya ingin mengkompilasi beberapa arahan berisi HTML secara manual. Apa yang setara dengan $compiledi Angular 2?

Misalnya, dalam Angular 1, saya dapat secara dinamis mengkompilasi sebuah fragmen HTML dan menambahkannya ke DOM:

var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);
pixelbits
sumber
8
Sebagian besar jawaban ini (kecuali 1 jawaban yang sudah tidak digunakan lagi) BUKAN sama dengan kompilasi $ 1 sudut. $ compile mengambil string HTML dan mengkompilasi komponen dan ekspresi yang terkandung di sana. Jawaban ini hanya menciptakan komponen yang telah ditentukan (yang belum dipakai) secara dinamis dan TIDAK BISA mengambil argumen string. Ini BUKAN hal yang sama. Adakah yang tahu jawaban nyata untuk pertanyaan ini?
danday74
Angular 4 datang dengan ComponentFactoryResolver yang setara dengan $ compile di Angular 1.0. Lihat jawaban saya stackoverflow.com/questions/34784778/…
Code-EZ
1
@ danday74 - Saya setuju bahwa tidak ada jawaban yang memberikan kemampuan untuk mengkompilasi template HTML yang sewenang-wenang, sebagai gantinya mereka hanya memilih dari serangkaian komponen yang sudah ada sebelumnya. Saya menemukan jawaban nyata di sini, yang berfungsi di Angular 8 setidaknya: stackoverflow.com/questions/61137899/… . Lihat satu jawaban, yang menyediakan StackBlitz yang berfungsi yang mengkompilasi templat HTML run-time-generate sewenang-wenang.
EbenH

Jawaban:

132

Angular 2.3.0 (2016-12-07)

Untuk mendapatkan semua detailnya periksa:

Untuk melihatnya dalam aksi:

Kepala sekolah:

1) Buat Template
2) Buat Komponen
3) Buat Modul
4) Kompilasi Modul
5) Buat (dan cache) ComponentFactory
6) gunakan Target untuk membuat Instance dari itu

Tinjauan singkat cara membuat Komponen

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

Cara cara menyuntikkan komponen ke 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;
}

Cuplikan kode cara membuat ComponentFactory (dan menyimpannya)

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

Cuplikan kode cara menggunakan hasil di atas

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

Deskripsi lengkap dengan semua perinciannya baca di sini , atau amati contoh kerja

.

.

OBSOLETE - Angular 2.0 RC5 related (RC5 only)

untuk melihat solusi sebelumnya untuk versi RC sebelumnya, silakan, cari melalui sejarah posting ini

Radim Köhler
sumber
2
Terima kasih banyak, saya sedang mencari contoh ComponentFactorydan ViewContainerRefuntuk menggantikan DynamicComponentLoader yang sekarang sudah tidak digunakan lagi.
Andre Loker
1
@ Maxou Itu adalah referensi lo-dash di index.html cukup tambahkan referensi itu dan semua akan bekerja
Radim Köhler
62
Apakah ini benar-benar sulit? Saya dulu hanya bisa melakukan sesuatu seperti ini: $compile($element.contents())($scope.$new());dan sekarang ini ratusan baris kode, lengkap dengan kreasi NgModule ... Ini adalah jenis hal yang membuat saya ingin menghindari NG2 dan beralih ke sesuatu yang lebih baik.
Karvapallo
2
Apa keuntungan dari menggunakan JitCompilerjika misalnya Anda mungkin bekerja dengan Compilerdari @angular/core? plnkr.co/edit/UxgkiT?p=preview
yurzui
4
Ya Tuhan, berapa banyak baris kode yang harus saya tulis hanya untuk mengkompilasi elemen kecil. Saya tidak mengerti dengan baik
Mr_Perfect
35

Catatan: Seperti @BennyBottema menyebutkan dalam komentar, DynamicComponentLoader sekarang sudah usang, maka demikian pula jawaban ini.


Angular2 tidak memiliki $ kompilasi yang setara. Anda dapat menggunakan DynamicComoponentLoaderdan meretas dengan kelas ES6 untuk mengkompilasi kode Anda secara dinamis (lihat bungkusan ini ):

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

Tetapi ini hanya akan berfungsi sampai html parser ada di dalam angular2 core.

alexpods
sumber
Trik yang luar biasa! tetapi jika komponen dinamis saya memiliki beberapa input, apakah mungkin untuk mengikat data dinamis juga?
Eugene Gluhotorenko
2
menjawab pertanyaan saya sendiri: dimungkinkan melalui mengirimkan data ke fungsi kompilasi. disini plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko
Solusi ini hanya bekerja dengan beta-0. Dari beta 1 hingga 15 kode contoh mengembalikan kesalahan. Kesalahan: Tidak ada arahan komponen pada elemen [objek objek]
Nicolas Forney
13
Sejak rc1 DynamicComponentLoader menjadi usang
Benny Bottema
1
@ BennyBottema karena DynamicComponentLoadersudah usang, bagaimana kita melakukan hal yang sama di Angular 2? Katakanlah saya memiliki dialog modal dan saya ingin secara dinamis memuat komponen baru karena isinya
Luke T O'Brien
16

Versi Sudut yang Saya Gunakan - Sudut 4.2.0

Angular 4 muncul dengan ComponentFactoryResolver untuk memuat komponen saat runtime. Ini adalah jenis implementasi yang sama dari $ compile di Angular 1.0 yang melayani kebutuhan Anda

Dalam contoh di bawah ini saya memuat komponen ImageWidget secara dinamis ke DashboardTileComponent

Untuk memuat komponen, Anda memerlukan arahan yang dapat Anda terapkan ke ng-template yang akan membantu menempatkan komponen dinamis

WidgetHostDirective

 import { Directive, ViewContainerRef } from '@angular/core';

    @Directive({
      selector: '[widget-host]',
    })
    export class DashboardTileWidgetHostDirective {
      constructor(public viewContainerRef: ViewContainerRef) { 


      }
    }

arahan ini menyuntikkan ViewContainerRef untuk mendapatkan akses ke wadah tampilan elemen yang akan menjadi tuan rumah komponen yang ditambahkan secara dinamis.

DashboardTileComponent (Komponen dudukan untuk merender komponen dinamis)

Komponen ini menerima input yang berasal dari komponen induk atau Anda dapat memuat dari layanan Anda berdasarkan implementasi Anda. Komponen ini melakukan peran utama untuk menyelesaikan komponen saat runtime. Dalam metode ini Anda juga dapat melihat metode bernama renderComponent () yang akhirnya memuat nama komponen dari layanan dan menyelesaikannya dengan ComponentFactoryResolver dan akhirnya menyetel data ke komponen dinamis.

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

Ini adalah pabrik layanan untuk mendaftarkan semua komponen yang ingin Anda selesaikan secara dinamis

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

ImageTextWidgetComponent (komponen kami memuat saat runtime)

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

Tambahkan Akhirnya tambahkan ImageTextWidgetComponent ini ke dalam modul aplikasi Anda sebagai 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;
    }

Referensi Asli dari blog saya

Dokumentasi Resmi

Unduh Kode Sumber Sampel

Kode-EZ
sumber
1
Anda lupa menyebutkan tentang entryComponents. Tanpanya solusi Anda tidak akan berfungsi
yurzui
ComponentFactoryResolver berada di angular2. Dan saya pikir itu tidak setara dengan $ compile
yurzui
@Yurzui. Tapi itu melayani kebutuhan $ compile kan ??
Code-EZ
@Yurzui Saya menggunakan jenis implementasi yang sama menggunakan $ compile. Ketika kita menghapus komponen entri dari modul itu akan membuang kesalahan ImageTextWidgetComponent tidak dimuat. Tetapi aplikasi masih berfungsi
Code-EZ
1
@BecarioSenior jika Anda tidak dilemparkan ke kelas model apa pun, itu akan menjadi dinamis default. Dalam contoh ini, tipe data properti adalah apa saja, yang berarti Anda dapat meneruskan data apa pun ke komponen dinamis sebagai input. Ini memberikan lebih banyak keterbacaan ke kode Anda.
Code-EZ
9

paket npm ini memudahkan saya: https://www.npmjs.com/package/ngx-dynamic-template

pemakaian:

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

Untuk membuat instance komponen secara dinamis dan melampirkannya ke DOM Anda, Anda dapat menggunakan skrip berikut dan bekerja di Angular RC :

template html:

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

Komponen pemuat

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

}
fabio_biondi
sumber
1
DynamicComponentLoader tidak lebih: '(Setelah itu tidak digunakan lagi, ada ComponentResolver. Dan sekarang ada ComponentFactoryResolver ( blog.rangle.io/dynamically-creating-components-with-angular-2 )
11mb
3

Angular TypeScript / ES6 (Angular 2+)

Bekerja dengan AOT + JIT sekaligus bersama-sama.

Saya membuat cara menggunakannya di sini: https://github.com/patrikx3/angular-compile

npm install p3x-angular-compile

Komponen: Seharusnya memiliki konteks dan beberapa data html ...

Html:

<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>
Patrik Laszlo
sumber
1
Tidak jelas apa arti judul 'Angular TypeScript'. Apakah solusi tidak berguna untuk ES5 dan ES6? Akan sangat membantu untuk memberikan contoh penggunaan terprogram dari paket ini, mitra langsung ke $compile(...)($scope). Tidak ada apa pun di dalamnya bahkan dalam repo readme.
Estus Flask
0

Saya tahu masalah ini sudah tua, tetapi saya menghabiskan waktu berminggu-minggu mencoba mencari cara untuk membuat pekerjaan ini dengan AOT diaktifkan. Saya dapat mengkompilasi objek tetapi tidak pernah dapat menjalankan komponen yang ada. Yah saya akhirnya memutuskan untuk mengubah kebijaksanaan, karena saya tidak mencari untuk mengkompilasi kode sebanyak mengeksekusi templat kustom. Pikir saya adalah untuk menambahkan html yang dapat dilakukan siapa saja dan loop melalui pabrik yang ada. Dengan melakukan itu saya dapat mencari elemen / atribut / etc. nama dan jalankan komponen pada HTMLElement itu. Saya bisa membuatnya bekerja dan berpikir saya harus membagikan ini untuk menyelamatkan orang lain berapa banyak waktu yang saya habiskan untuk itu.

@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
            });
        });
    }
}
Vince
sumber
-5

Jika Anda ingin menyuntikkan kode html gunakan direktif

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

Jika Anda ingin memuat seluruh komponen di suatu tempat, gunakan DynamicComponentLoader:

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

Vlado Tesanovic
sumber
2
Saya ingin menyuntikkan fragmen HTML sebagai string dan meneruskannya ke kompiler komponen, dan kemudian menambahkan komponen itu di DOM saya. Bisakah Anda memberi contoh bagaimana solusi Anda bisa bekerja?
pixelbits
3
menggunakan innerHtml tidak mengkompilasi komponen apa pun di dalam htmlVar
Juanín