Еквівалент компіляції $ у куті 2


134

Я хочу вручну зібрати деякі директиви, що містять HTML. Який еквівалент $compileу куті 2?

Наприклад, у Angular 1 я міг динамічно скласти фрагмент HTML і додати його до DOM:

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

8
Більшість цих відповідей (крім 1 застарілої відповіді) НЕ є еквівалентом кутового 1 $ компіляції. $ compile займає рядок HTML і компілює компоненти та вирази, що містяться там. Ці відповіді просто створюють заздалегідь визначені компоненти (які ще не встановлені) динамічно і CANNOT приймає аргумент рядка. Це НЕ те саме. Хтось знає справжню відповідь на це питання?
понеділок74


Angular 4 придумав ComponentFactoryResolver, який еквівалентний $ компіляції у Angular 1.0. Дивіться мою відповідь stackoverflow.com/questions/34784778/…
Code-EZ

1
@ danday74 - Я погоджуюся, що жоден із цих відповідей не забезпечує можливість складання довільних шаблонів HTML, а натомість вони просто вибирають із набору вже існуючих компонентів. Тут я знайшов справжню відповідь, яка працює принаймні у Angular 8: stackoverflow.com/questions/61137899/… . Дивіться одну відповідь, яка забезпечує робочий StackBlitz, який збирає довільний HTML-шаблон, створений під час виконання.
EbenH

Відповіді:


132

Кутовий 2.3.0 (2016-12-07)

Щоб отримати всі деталі, перевірте:

Щоб побачити це в дії:

Принципи:

1) Створіть шаблон
2) Створіть компонент
3) Створіть модуль
4) Складіть модуль
5) Створіть (і кешуйте) КомпонентФактори
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 - кутовий 2.0 RC5, пов'язаний (лише RC5)

щоб побачити попередні рішення попередніх версій RC, будь ласка, перегляньте історію цієї публікації


2
Велике спасибі, я шукав робочий приклад ComponentFactoryта ViewContainerRefзамінив застарілий DynamicComponentLoader.
Андре Локер

1
@maxou Це посилання на dash у index.html, просто додайте цю посилання, і все спрацює
Радім Келер

62
Чи справді це складно? Раніше я міг просто зробити щось подібне: $compile($element.contents())($scope.$new());а тепер це сотні рядків коду, укомплектовані створенням NgModule ... Це та річ, яка змушує мене піти подалі від NG2 і перейти до чогось кращого.
Карвапалло

2
Що таке перевага використання , JitCompilerякщо ваш приклад може працювати з Compilerвід @angular/core? plnkr.co/edit/UxgkiT?p=preview
yurzui

4
Боже мій, скільки рядків коду я повинен написати лише для складання невеликого елемента. Я не добре зрозумів
Mr_Perfect

35

Примітка. Як згадує @BennyBottema у коментарі, DynamicComponentLoader тепер застарілий, тому така відповідь.


Angular2 не має жодного еквівалента $ компіляції . Ви можете використовувати DynamicComoponentLoaderта рубати з класами ES6 для динамічного складання коду (див. Цей планк ):

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
Євген Глухотеренко

Це рішення працює лише з бета-0. З бета-версії 1 до 15 приклад коду повертає помилку. Помилка: в елементі [object Object] немає компонентної директиви
Nicolas Forney

13
Оскільки rc1 DynamicComponentLoader став застарілим
Бенні Боттема

1
@BennyBottema оскільки DynamicComponentLoaderзастаріла, як ми можемо робити те саме, що в Angular 2? Скажіть, у мене є модальний діалог, і я хочу динамічно завантажувати новий компонент у міру вмісту
Luke T O'Brien

16

Кутова версія, яку я використав - Кутова 4.2.0

Кутовий 4 придумав ComponentFactoryResolver для завантаження компонентів під час виконання. Це свого роду реалізація $ compile в Angular 1.0, яка відповідає вашим потребам

У цьому нижче прикладі я динамічно завантажую компонент ImageWidget у DashboardTileComponent

Для завантаження компонента вам потрібна директива, яку ви можете застосувати до ng-template, який допоможе розмістити динамічний компонент

WidgetHostDirective

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

    }
}

TileModel

 export interface TileModel {
      data: any;
    }

Оригінальна довідка з мого блогу

Офіційна документація

Завантажте зразковий вихідний код


1
Ви забули згадати про entryComponents. Без нього ваше рішення не буде працювати
yurzui

КомпонентFactoryResolver знаходився в кутку2. І я думаю, що це не еквівалент $ compile
yurzui

@yurzui. Але це відповідає потребі $ компілювати правильно ??
Code-EZ

@yurzui Мені застосовано той самий варіант реалізації за допомогою $ compile. Коли ми видаляємо компоненти входу з модуля, він видасть помилку ImageTextWidgetComponent не завантажується. Але додаток все ще працює
Code-EZ

1
@BecarioSenior, якщо ви не призначені для будь-якого модельного класу, це буде динамікою за замовчуванням. У цьому прикладі тип даних про властивості є будь-яким, а це означає, що ви можете передавати будь-які дані в динамічний компонент як вхідні дані. Це надасть більшої читабельності вашого коду.
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

Кутовий TypeScript / ES6 (кутовий 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" title. Чи рішення марне для ES5 та ES6? Було б корисно навести приклад програмного використання цього пакету, прямого аналога $compile(...)($scope). Нічого в цьому немає навіть у репо-римме.
колба Естуса


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. Чи можете ви навести приклад того, як може працювати будь-яке з ваших рішень?
пікселі

3
Використання innerHtml не збирає жодних компонентів всередині htmlVar
Juanín
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.