Equivalente a $ compile no Angular 2

134

Eu quero compilar manualmente alguns diretórios que contêm HTML. Qual é o equivalente $compileem Angular 2?

Por exemplo, no Angular 1, eu poderia compilar dinamicamente um fragmento de HTML e anexá-lo ao DOM:

var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);
pixelbits
fonte
8
A maioria dessas respostas (exceto uma resposta agora obsoleta) NÃO é equivalente à compilação angular de 1 $. $ compile pega uma string HTML e compila componentes e expressões contidas nela. Essas respostas simplesmente criam componentes predefinidos (que ainda não foram instanciados) dinamicamente e NÃO PODEM usar um argumento de string. Isto não é a mesma coisa. Alguém sabe a resposta real para esta pergunta?
precisa saber é o seguinte
Angular 4 veio com ComponentFactoryResolver que equivale a US $ compilação em angular 1,0 .Veja minha resposta stackoverflow.com/questions/34784778/...
Code-EZ
1
@ danday74 - Concordo que nenhuma dessas respostas fornece a capacidade de compilar modelos HTML arbitrários, mas apenas seleciona um conjunto de componentes pré-existentes. Encontrei a resposta real aqui, que funciona pelo menos no Angular 8: stackoverflow.com/questions/61137899/… . Veja a resposta, que fornece um StackBlitz funcional que compila um modelo HTML arbitrário gerado em tempo de execução.
EbenH 20/07

Respostas:

132

Angular 2.3.0 (07-12-2016)

Para obter todos os detalhes, verifique:

Para ver isso em ação:

Os diretores:

1) Criar modelo
2) Criar componente
3) Criar módulo
4) Compilar módulo
5) Criar (e armazenar em cache) ComponentFactory
6) use o Target para criar uma instância dele

Uma rápida visão geral de como criar um componente

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

Uma maneira de injetar componentes no 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;
}

Um trecho de código como criar um ComponentFactory (e armazená-lo em cache)

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

Um trecho de código como usar o resultado acima

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

A descrição completa com todos os detalhes pode ser lida aqui ou observe o exemplo de trabalho

.

.

OBSOLETE - Angular 2.0 RC5 relacionado (apenas RC5)

para ver as soluções anteriores das versões anteriores do RC, pesquise o histórico deste post

Radim Köhler
fonte
2
Muito obrigado, eu estava procurando um exemplo de trabalho ComponentFactorye ViewContainerRefpara substituir o DynamicComponentLoader agora obsoleto.
Andre Loker 10/05
1
referência @maxou É lo-traço no index.html basta adicionar essa referência e todos irão trabalhar
Radim Köhler
62
Isso é realmente tão difícil? Eu costumava fazer algo assim: $compile($element.contents())($scope.$new());e agora são centenas de linhas de código, completas com a criação do NgModule ... Esse é o tipo de coisa que me faz querer ficar longe do NG2 e seguir para algo melhor.
22416 Karvapallo
2
Qual é a vantagem de usar JitCompilerse seu exemplo funcionar com Compilerfrom @angular/core? plnkr.co/edit/UxgkiT?p=preview
yurzui
4
Oh meu Deus, quantas linhas de código eu deveria escrever apenas para compilar um pequeno elemento. Eu não entendi bem
Mr_Perfect
35

Nota: Como o @BennyBottema menciona em um comentário, o DynamicComponentLoader agora está obsoleto, portanto, essa é a resposta.


Angular2 não tem nenhum equivalente de compilação $ . Você pode usar DynamicComoponentLoadere cortar com aulas ES6 para compilar o código dinamicamente (ver este 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'
    );
  }
}

Mas ele funcionará apenas até o analisador html estar dentro do núcleo angular2.

alexpods
fonte
Truque impressionante! mas, caso meu componente dinâmico tenha algumas entradas, é possível vincular dados dinâmicos também?
Eugene Gluhotorenko 6/03/16
2
respondendo à minha própria pergunta: é possível passando dados para a função de compilação. aqui o plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko
Esta solução está funcionando apenas com beta-0. De beta 1 a 15, o código de exemplo retorna um erro. Erro: Não há directiva nenhum componente no elemento [Object objeto]
Nicolas Forney
13
Desde rc1 DynamicComponentLoader tornou-se obsoleto
Benny Bottema
1
@BennyBottema, uma vez que DynamicComponentLoaderestá obsoleta, como fazemos o mesmo tipo de coisa no Angular 2? Digamos que eu tenha um diálogo modal e eu quero carregar dinamicamente um novo componente como o seu conteúdo
Luke T O'Brien
16

Versão angular que usei - Angular 4.2.0

O Angular 4 veio com o ComponentFactoryResolver para carregar componentes no tempo de execução. Este é um tipo da mesma implementação do $ compile no Angular 1.0 que atende à sua necessidade

Neste exemplo abaixo, estou carregando o componente ImageWidget dinamicamente em um DashboardTileComponent

Para carregar um componente, você precisa de uma diretiva que possa ser aplicada ao ng-template que ajudará a colocar o componente dinâmico

WidgetHostDirective

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

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


      }
    }

essa diretiva injeta ViewContainerRef para obter acesso ao contêiner de exibição do elemento que hospedará o componente adicionado dinamicamente.

DashboardTileComponent (componente de suporte de local para renderizar o componente dinâmico)

Este componente aceita uma entrada proveniente dos componentes pai ou você pode carregar do seu serviço com base na sua implementação. Este componente está desempenhando o papel principal de resolver os componentes em tempo de execução. Nesse método, você também pode ver um método chamado renderComponent () que, finalmente, carrega o nome do componente de um serviço e é resolvido com ComponentFactoryResolver e, finalmente, configurando dados para o componente dinâmico.

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

Esta é uma fábrica de serviços para registrar todos os componentes que você deseja resolver dinamicamente

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

ImageTextWidgetComponent (componente que estamos carregando em tempo de execução)

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

Adicionar Finalmente, adicione este ImageTextWidgetComponent ao seu módulo de aplicativo como 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;
    }

Referência original do meu blog

Documentação oficial

Baixar código-fonte de amostra

Code-EZ
fonte
1
Você esqueceu de mencionar entryComponents. Sem ele sua solução não vai funcionar
yurzui
ComponentFactoryResolver estava em angular2. E eu acho que não é equivalente a US $ compilação
yurzui
@yurzui. Mas serve a necessidade de compilar $ certo ??
Code-EZ
@yurzui Fui usado o mesmo tipo de implementação usando $ compile. Quando removemos os componentes de entrada do módulo, ele gera um erro ImageTextWidgetComponent não é carregado. Mas aplicativo ainda funciona
Code-EZ
1
@BecarioSenior, se você não converter para nenhuma classe de modelo, será a dinâmica padrão. Neste exemplo, o tipo de dados da propriedade é any, o que significa que você pode passar quaisquer dados para o componente dinâmico como entrada. É dar mais legibilidade ao seu código.
Code-EZ
9

este pacote npm facilitou para mim: https://www.npmjs.com/package/ngx-dynamic-template

uso:

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

Para criar dinamicamente uma instância de um componente e anexá-la ao seu DOM, você pode usar o seguinte script e deve funcionar no Angular RC :

modelo html:

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

Componente carregador

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
fonte
1
O DynamicComponentLoader não existe mais: '(Depois disso foi preterido, houve ComponentResolver. E agora há o ComponentFactoryResolver ( blog.rangle.io/dynamically-creating-components-with-angular-2 )
11mb
3

Angular TypeScript / ES6 (Angular 2+)

Trabalha com AOT + JIT de uma vez juntos.

Eu criei como usá-lo aqui: https://github.com/patrikx3/angular-compile

npm install p3x-angular-compile

Componente: deve ter um contexto e alguns dados html ...

Html:

<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>
Patrik Laszlo
fonte
1
Não é óbvio o que significa o título 'Angular TypeScript'. A solução é inútil para ES5 e ES6? Seria útil fornecer o exemplo do uso programático deste pacote, uma contraparte direta do $compile(...)($scope). Não há nada nele, mesmo no repositório readme.
Estus Flask
0

Sei que esse problema é antigo, mas passei semanas tentando descobrir como fazer isso funcionar com o AOT ativado. Consegui compilar um objeto, mas nunca consegui executar componentes existentes. Bem, finalmente decidi mudar de tato, pois não estava procurando compilar código, mas executar um modelo personalizado. Meu pensamento era adicionar o html que qualquer um pode fazer e fazer um loop pelas fábricas existentes. Ao fazer isso, posso procurar o elemento / atributo / etc. nomes e execute o componente nesse HTMLElement. Consegui fazê-lo funcionar e achei que deveria compartilhar isso para economizar a outra pessoa a imensa quantidade de tempo que gastei nele.

@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
fonte
-5

Se você deseja injetar código html, use a diretiva

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

Se você deseja carregar o componente inteiro em algum lugar, use DynamicComponentLoader:

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

Vlado Tesanovic
fonte
2
Eu quero injetar um fragmento de HTML como uma string e passá-lo para um compilador de componentes e, em seguida, acrescentar esse componente no meu DOM. Você pode dar um exemplo de como qualquer uma das suas soluções pode funcionar?
pixelbits
3
using innerHtml não compila nenhum componente dentro do htmlVar
Juanín