Angular 2'de $ compile eşdeğeri


134

Bazı HTML içeren yönergeleri manuel olarak derlemek istiyorum. $compileAngular 2'deki karşılığı nedir ?

Örneğin, Angular 1'de, dinamik olarak bir HTML parçası derleyebilir ve bunu DOM'a ekleyebilirim:

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

8
Bu yanıtların çoğu (şu anda kullanımdan kaldırılmış 1 yanıt dışında) açısal 1 $ derlemesine eşdeğer DEĞİLDİR. $ compile bir HTML dizesi alır ve burada bulunan bileşenleri ve ifadeleri derler. Bu yanıtlar, dinamik olarak (henüz somutlaştırılmamış) önceden tanımlanmış bileşenler oluşturur ve bir dizi argümanı ALAMAZ. Bu aynı şey DEĞİLDİR. Bu sorunun gerçek cevabını bilen var mı?
danday74


Angular 4, Angular 1.0'da $ derlemeye eşdeğer olan ComponentFactoryResolver ile geldi. Cevabıma bakın stackoverflow.com/questions/34784778/…
Code-EZ

1
@ danday74 - Bu yanıtların hiçbirinin keyfi HTML şablonlarını derleme yeteneği sağlamadığını, bunun yerine önceden var olan bir dizi bileşenden birini seçtiklerini kabul ediyorum. En azından Angular 8'de çalışan gerçek yanıtı burada buldum: stackoverflow.com/questions/61137899/… . Çalışma zamanında rastgele oluşturulmuş bir HTML şablonunu derleyen çalışan bir StackBlitz sağlayan tek yanıta bakın.
EbenH

Yanıtlar:


132

Açısal 2.3.0 (2016-12-07)

Tüm ayrıntıları kontrol etmek için:

Bunu iş başında görmek için:

Müdürler:

1) Şablon Oluşturun
2) Bileşen Oluşturun
3) Modül Oluşturun
4) Modülü Derleyin
5) ComponentFactory oluşturun (ve önbelleğe alın)
6) bunun bir Örneğini oluşturmak için Hedef kullanın

Bir Bileşenin nasıl oluşturulacağına hızlı bir genel bakış

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'e bileşen enjekte etmenin bir yolu

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

Bir kod parçacığı nasıl oluşturulur ComponentFactory (ve önbelleğe alınır)

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

Yukarıdaki sonucun nasıl kullanılacağı bir kod parçası

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

Tüm ayrıntılarla birlikte tam açıklama burada okuyun veya çalışma örneğini inceleyin

.

.

OBSOLETE - Angular 2.0 RC5 ile ilgili (yalnızca RC5)

önceki RC sürümleri için önceki çözümleri görmek için lütfen bu yazının geçmişinde arama yapın


2
Çok teşekkürler, ben bir uygulama örneğinin arıyordu ComponentFactoryve ViewContainerRefşimdi artık yok DynamicComponentLoader yerine.
Andre Loker

1
@maxou Bu, index.html'deki lo-dash referansıdır, sadece bu referansı ekleyin ve her şey işe yarayacaktır
Radim Köhler

62
Bu gerçekten bu kadar zor mu? Eskiden böyle bir şey yapabiliyordum: $compile($element.contents())($scope.$new());ve şimdi NgModule oluşturma ile birlikte yüzlerce satır kod var ... Bu, NG2'den uzaklaşıp daha iyi bir şeye geçmeme neden olan türden bir şey.
Karvapallo

2
Kullanmanın avantajı nedir JitCompilersenin örneğin ile işe yarayabilecek eğer Compilergelen @angular/core? plnkr.co/edit/UxgkiT?p=preview
yurzui

4
Aman tanrım, küçük bir öğeyi derlemek için kaç satır kod yazmalıyım? İyi anlamadım
Mr_Perfect

35

Not: @BennyBottema'nın bir yorumda bahsettiği gibi, DynamicComponentLoader artık kullanımdan kaldırıldı, dolayısıyla bu cevap da öyle.


Angular2'nin herhangi bir $ compile eşdeğeri yoktur. Sen kullanabilirsiniz DynamicComoponentLoaderve dinamik kodunuzu derlemek için ES6 sınıfları ile kesmek (Bunu görmek Düşmek ):

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

Ancak yalnızca html ayrıştırıcısı angular2 çekirdeğinin içine girene kadar çalışacaktır.


Harika numara! ancak dinamik bileşenimin bazı girdileri olması durumunda dinamik verileri de bağlamak mümkün müdür?
Eugene Gluhotorenko

2
kendi soruma cevap veriyorum: verileri derleme işlevine geçirmekle mümkündür. burada plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko

Bu çözüm yalnızca beta-0 ile çalışıyor. Beta 1'den 15'e kadar örnek kod bir hata döndürür. Hata: [object Object] öğesinde bileşen yönergesi yok
Nicolas Forney

13
Rc1

1
@BennyBottema DynamicComponentLoaderkullanımdan kaldırıldığından beri , aynı şeyleri Angular 2'de nasıl yaparız? Diyelim ki kalıcı bir iletişim kutusu var ve yeni bir bileşeni içeriği olduğu gibi dinamik olarak yüklemek istiyorum
Luke T O'Brien

16

Kullandığım Açısal Sürüm - Angular 4.2.0

Angular 4, bileşenleri çalışma zamanında yüklemek için ComponentFactoryResolver ile geldi . Bu, ihtiyaçlarınızı karşılayan Angular 1.0'daki $ compile ile aynı uygulamasıdır.

Aşağıdaki örnekte ImageWidget bileşenini DashboardTileComponent'e dinamik olarak yüklüyorum

Bir bileşeni yüklemek için , dinamik bileşeni yerleştirmeye yardımcı olacak ng-şablonuna uygulayabileceğiniz bir yönergeye ihtiyacınız vardır.

WidgetHostDirective

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

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


      }
    }

bu yönerge , dinamik olarak eklenen bileşeni barındıracak öğenin görüntüleme kabına erişim sağlamak için ViewContainerRef'i enjekte eder .

DashboardTileComponent (Dinamik bileşeni oluşturmak için tutucu bileşeni yerleştirin)

Bu bileşen, bir üst bileşenden gelen bir girdiyi kabul eder veya uygulamanıza bağlı olarak hizmetinizden yükleyebilirsiniz. Bu bileşen, bileşenleri çalışma zamanında çözmek için önemli bir rol oynamaktadır. Bu yöntemde, sonuçta bir hizmetten bileşen adını yükleyen ve ComponentFactoryResolver ile çözümleyen ve son olarak verileri dinamik bileşene ayarlayan renderComponent () adlı bir yöntemi de görebilirsiniz .

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

Bu, dinamik olarak çözmek istediğiniz tüm bileşenleri kaydettirmek için bir hizmet fabrikasıdır

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

ImageTextWidgetComponent (çalışma zamanında yüklediğimiz bileşen)

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

Ekle Sonunda bu ImageTextWidgetComponent öğesini uygulama modülünüze entryComponent olarak ekleyin

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

Blogumdan Orjinal Referans

Resmi Belgeler

Örnek Kaynak Kodunu İndirin


1
Bahsetmeyi unuttun entryComponents. Onsuz çözümünüz işe yaramaz
yurzui

ComponentFactoryResolver açısal2 idi. Ve bunun $ compile ile eşdeğer olmadığını düşünüyorum
yurzui

@yurzui. Ama $ compile ihtiyacına hizmet ediyor değil mi?
Code-EZ

@yurzui Ben de $ compile kullanarak aynı türden bir uygulama kullandım. Giriş bileşenlerini modülden kaldırdığımızda, ImageTextWidgetComponent yüklenmediğinde bir hata verecektir. Ancak uygulama hala çalışıyor
Code-EZ

1
@BecarioSenior herhangi bir model sınıfına dönüştürülmezseniz, varsayılan dinamik olacaktır. Bu örnekte, özellik verilerinin türü herhangi biri olup, bu, herhangi bir veriyi girdi olarak dinamik bileşene iletebileceğiniz anlamına gelir. Kodunuza daha fazla okunabilirlik sağlar.
Code-EZ

9

bu npm paketi benim için kolaylaştırdı: https://www.npmjs.com/package/ngx-dynamic-template

kullanımı:

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

3

Bir bileşenin bir örneğini dinamik olarak oluşturmak ve DOM'nuza eklemek için aşağıdaki komut dosyasını kullanabilirsiniz ve Angular RC'de çalışmalısınız :

html şablonu:

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

Yükleyici bileşeni

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 artık yok: '(Bundan sonra, ComponentResolver vardı. Ve şimdi ComponentFactoryResolver var ( blog.rangle.io/dynamically-creating-components-with-angular-2 )
11mb

3

Açısal TypeScript / ES6 (Açısal 2+)

AOT + JIT ile aynı anda çalışır.

Nasıl kullanılacağını burada oluşturdum: https://github.com/patrikx3/angular-compile

npm install p3x-angular-compile

Bileşen: Bir bağlam ve bazı html verileri olmalı ...

Html:

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

1
'Angular TypeScript' başlığının ne anlama geldiği açık değil. Çözüm ES5 ve ES6 için işe yaramaz mı? Bu paketin programlı kullanımına ilişkin bir örnek vermek faydalı olacaktır $compile(...)($scope). Repo beni oku'da bile üzerinde hiçbir şey yok.
Estus Flask


0

Bu sorunun eski olduğunu biliyorum, ancak AOT etkinken bu sorunu nasıl çözeceğimi bulmak için haftalarca uğraştım. Bir nesneyi derleyebildim ancak mevcut bileşenleri hiçbir zaman çalıştıramadım. Sonunda özel bir şablon yürütmek kadar kod derlemeyi düşünmediğim için inceliğini değiştirmeye karar verdim. Benim düşüncem, herkesin yapabileceği ve mevcut fabrikalarda dolaşabileceği html'yi eklemekti. Bunu yaparken element / attribute / etc için arama yapabilirim. bu HTMLElement üzerinde bileşeni adlandırır ve çalıştırır. Onu çalıştırmayı başardım ve bunu bir başkasına harcadığım muazzam miktarda zaman kazanmak için paylaşmam gerektiğini düşündüm.

@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 kodunu enjekte etmek istiyorsanız yönergeyi kullanın

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

Tüm bileşeni bir yerde yüklemek istiyorsanız, DynamicComponentLoader'ı kullanın:

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


2
Bir HTML parçasını dize olarak enjekte etmek ve bunu bir bileşen derleyicisine iletmek ve ardından bu bileşeni DOM'uma eklemek istiyorum. Çözümlerinizden herhangi birinin nasıl çalışabileceğine dair bir örnek verebilir misiniz?
pixelbits

3
innerHtml kullanmak htmlVar içinde herhangi bir bileşeni derlemez
Juanín
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.