Эквивалент $compile в Angular 2
Я хочу вручную скомпилировать некоторые HTML-директивы. Что эквивалентно $compile
в угловых 2?
Например, в Angular 1 я мог динамически скомпилировать фрагмент HTML и добавить его в DOM:
var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);
7 ответов
Угловой 2.3.0 (2016-12-07)
Чтобы получить все детали, проверьте:
Чтобы увидеть это в действии:
- наблюдать за работающим поршнем (работает с 2.3.0+)
Принципы:
1) Создать шаблон
2) Создать компонент
3) Создать модуль
4) Модуль компиляции
5) Создать (и кешировать) ComponentFactory
6) использовать Target, чтобы создать его экземпляр
Краткий обзор, как создать Компонент
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, пожалуйста, просмотрите историю этого поста
Примечание: как @BennyBottema упоминает в комментарии, DynamicComponentLoader теперь не рекомендуется, как и этот ответ.
Angular2 не имеет эквивалента $ compile. Ты можешь использовать 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.
Угловая версия, которую я использовал - Angular 4.2.0
Angular 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;
}
Этот пакет 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>
Чтобы динамически создать экземпляр компонента и прикрепить его к вашему 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);
}
}
Угловой 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>
Вы можете увидеть компонент, который позволяет компилировать простые динамические компоненты Angular https://www.npmjs.com/package/@codehint-ng/html-compiler
Я знаю, что эта проблема устарела, но я потратил недели, пытаясь понять, как заставить ее работать с включенным 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
});
});
}
}
Если вы хотите внедрить HTML-код, используйте директиву
<div [innerHtml]="htmlVar"></div>
Если вы хотите загрузить весь компонент в каком-то месте, используйте DynamicComponentLoader:
https://angular.io/docs/ts/latest/api/core/DynamicComponentLoader-class.html