Я хочу динамически создать шаблон. Это необходимо использовать для создания ComponentType
в Время выполнения и разместить (даже заменить) его где-нибудь внутри хостингового компонента.
До RC4 я использовал ComponentResolver
, но с RC5 я получил сообщение:
ComponentResolver
устарел для динамической компиляции. Вместо этого используйтеComponentFactoryResolver
вместе с поставщиком@NgModule/@Component.entryComponents
или ANALYZE_FOR_ENTRY_COMPONENTS. Только для компиляции во время выполнения вы также можете использоватьCompiler.compileComponentSync/Async
.
Я нашел этот (официальный angular2) документ
И поймите, что я могу использовать либо
ngIf
с ComponentFactoryResolver
. Если я передам известные компоненты в хостинг один внутри @Component({entryComponents: [comp1, comp2], ...})
, я могу использовать .resolveComponentFactory(componentToRender);
Compiler
...Но вопрос в том, как использовать этот Compiler
? В примечании выше говорится, что я должен позвонить: Compiler.compileComponentSync/Async
- так как?
Например. Я хочу создать (на основе некоторых условий конфигурации) этот тип шаблона для одного вида настроек
<form>
<string-editor
[propertyName]="'code'"
[entity]="entity"
></string-editor>
<string-editor
[propertyName]="'description'"
[entity]="entity"
></string-editor>
...
а в другом случае этот (string-editor
заменяется на text-editor
)
<form>
<text-editor
[propertyName]="'code'"
[entity]="entity"
></text-editor>
...
И так далее (разные числа/дата/ссылка editors
по типам свойств, пропустили некоторые свойства для некоторых пользователей...). То есть это пример, реальная конфигурация может генерировать гораздо более разные и сложные шаблоны.
Шаблон меняет, поэтому я не могу использовать ComponentFactoryResolver
и передавать существующие... Мне нужно решение с Compiler
Вы хотите использовать эти функции с AOT (компиляция заранее)? Вы получаете:
Ошибка: ошибка обнаружила статические значения разрешения символов. Вызов функций не поддерживается. Рассмотрите возможность замены функции или лямбда ссылкой на экспортированную функцию (позиция 65:17 в исходном файле .ts), разрешение символа COMPILER_PROVIDERS в... / node_modules/@angular/compiler/src/compiler.d.ts,
Пожалуйста, оставьте свой комментарий, проголосовате здесь:
ПРИМЕЧАНИЕ. Чтобы получить решение для предыдущей версии, проверьте историю этого сообщения.
Аналогичная тема обсуждается здесь Эквивалент компиляции $в Angular 2. Нам нужно использовать JitCompiler
и NgModule
. Подробнее о NgModule
в Angular2 здесь:
Существует рабочий плункер/пример (динамический шаблон, динамический тип компонента, динамический модуль, JitCompiler
,... в действии)
Принцип:
1) создать шаблон
2) найдите ComponentFactory
в кеше - перейдите к 7)
3) - создать Component
4) - создать Module
5) - скомпилировать Module
6) - возврат (и кеш для последующего использования) ComponentFactory
7) используйте Цель и ComponentFactory
для создания экземпляра динамического Component
Вот фрагмент кода (более здесь). Наш пользовательский Builder возвращает только встроенный/кэшированный ComponentFactory
и объект Target placeholder для создания экземпляра DynamicComponent
// here we get a TEMPLATE with dynamic content === TODO
var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);
// 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 inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
Это он - в двух словах. Чтобы получить более подробную информацию, читайте ниже
.
Наблюдайте за плункером и возвращайтесь к чтению деталей, если какой-то фрагмент требует больше объяснений
.
Ниже этот сценарий, мы будем
PartsModule:NgModule
(держатель небольших кусков)DynamicModule:NgModule
, который будет динамически содержать динамический компонент (и ссылку PartsModule
)Component
(только если шаблон был изменен)RuntimeModule:NgModule
. Этот модуль будет содержать ранее созданный тип Component
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
, чтобы получить ComponentFactory
DynamicComponent
- задания заполнителя View Target и ComponentFactory
@Inputs
новому экземпляру (переключитесь с INPUT
на TEXTAREA
редактирование), потребляйте @Outputs
Нам понадобится NgModule
s.
Пока я хотел бы показать очень простой пример, в этом случае мне понадобится три модуля (на самом деле 4, но я не считаю AppModule). Пожалуйста, возьмите это, а не простой фрагмент в качестве основы для действительно твердого динамического генератора компонент.
Для всех небольших компонентов будет один. string-editor
, text-editor
(date-editor
, number-editor
...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Где
DYNAMIC_DIRECTIVES
расширяемы и предназначены для хранения всех мелких деталей, используемых для нашего динамического шаблона/типа компонента. Проверьте приложение /parts/parts.module.ts
Второй будет модулем для обработки динамических файлов. Он будет содержать компоненты хостинга и некоторые провайдеры. Это будут синглеты. Поэтому мы опубликуем их стандартным способом - с помощью forRoot()
import { DynamicDetail } from './detail.view';
import { DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';
@NgModule({
imports: [ PartsModule ],
declarations: [ DynamicDetail ],
exports: [ DynamicDetail],
})
export class DynamicModule {
static forRoot()
{
return {
ngModule: DynamicModule,
providers: [ // singletons accross the whole app
DynamicTemplateBuilder,
DynamicTypeBuilder
],
};
}
}
Проверьте использование
forRoot()
вAppModule
Наконец, нам понадобится adhoc, runtime module.. но это будет создано позже, как часть задания DynamicTypeBuilder
.
Четвертым модулем, модулем приложения является тот, кто продолжает объявлять поставщиков компилятора:
...
import { COMPILER_PROVIDERS } from '@angular/compiler';
import { AppComponent } from './app.component';
import { DynamicModule } from './dynamic/dynamic.module';
@NgModule({
imports: [
BrowserModule,
DynamicModule.forRoot() // singletons
],
declarations: [ AppComponent],
providers: [
COMPILER_PROVIDERS // this is an app singleton declaration
],
Читайте (читайте) гораздо больше о NgModule:
В нашем примере мы обработаем деталь этого типа
entity = {
code: "ABC123",
description: "A description of this Entity"
};
Чтобы создать template
, в этом plunker мы используем этот простой/наивный строитель.
Реальное решение, настоящий шаблонный строитель, - это то место, где ваше приложение может многое сделать
// plunker - app/dynamic/template.builder.ts
import {Injectable} from "@angular/core";
@Injectable()
export class DynamicTemplateBuilder {
public prepareTemplate(entity: any, useTextarea: boolean){
let properties = Object.keys(entity);
let template = "<form >";
let editorName = useTextarea
? "text-editor"
: "string-editor";
properties.forEach((propertyName) =>{
template += `
<${editorName}
[propertyName]="'${propertyName}'"
[entity]="entity"
></${editorName}>`;
});
return template + "</form>";
}
}
Трюк здесь - он создает шаблон, который использует некоторый набор известных свойств, например. entity
. Такое свойство (-ies) должно быть частью динамического компонента, который мы создадим дальше.
Чтобы сделать это немного проще, мы можем использовать интерфейс для определения свойств, которые может использовать наш конструктор шаблонов. Это будет реализовано с помощью нашего динамического типа Component.
export interface IHaveDynamicData {
public entity: any;
...
}
ComponentFactory
Очень важно здесь иметь в виду:
наш тип компонента, построенный с помощью нашего
DynamicTypeBuilder
, может отличаться, но только по его шаблону (созданному выше). Свойства компонентов (входы, выходы или некоторые защищенные) все те же. Если нам нужны разные свойства, мы должны определить другую комбинацию Template и Type Builder
Итак, мы трогаем суть нашего решения. Builder будет 1) создать ComponentType
2) создать свой NgModule
3) компилировать ComponentFactory
4) кеш для последующего повторного использования.
Зависимость, которую мы должны получить:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
И вот фрагмент, как получить ComponentFactory
:
// plunker - app/dynamic/type.builder.ts
// this object is singleton - so we can use this as a cache
private _cacheOfFactories:
{[templateKey: string]: ComponentFactory<IHaveDynamicData>} = {};
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 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);
});
});
}
Выше мы создаем и кеш как
Component
, так иModule
. Потому что, если шаблон (фактически реальная динамическая часть всего этого) тот же.. мы можем повторно использовать
И вот два метода, которые представляют собой действительно классный способ создания украшенных классов/типов во время выполнения. Не только @Component
, но и @NgModule
protected 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;
}
protected createComponentModule (componentType: any) {
@NgModule({
imports: [
PartsModule, // there are 'text-editor', 'string-editor'...
],
declarations: [
componentType
],
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
Важно:
наши динамические типы компонентов различаются, но только по шаблону. Поэтому мы используем этот факт для кэширования. Это действительно очень важно. Angular2 также кэширует эти.. с помощью типа. И если мы воссоздаем для тех же шаблонов строки новые типы..., мы начнем генерировать утечки памяти.
ComponentFactory
, используемый компонентом хостингаЗаключительная часть - это компонент, в котором размещается цель для нашего динамического компонента, например. <div #dynamicContentPlaceHolder></div>
. Мы получаем ссылку на него и используем ComponentFactory
для создания компонента. Это в двух словах, и вот все части этого компонента (при необходимости откройте plunker здесь)
Пусть сначала суммирует операторы импорта:
import {Component, ComponentRef,ViewChild,ViewContainerRef} from '@angular/core';
import {AfterViewInit,OnInit,OnDestroy,OnChanges,SimpleChange} from '@angular/core';
import { IHaveDynamicData, DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';
@Component({
selector: 'dynamic-detail',
template: `
<div>
check/uncheck to use INPUT vs TEXTAREA:
<input type="checkbox" #val (click)="refreshContent(val.checked)" /><hr />
<div #dynamicContentPlaceHolder></div> <hr />
entity: <pre>{{entity | json}}</pre>
</div>
`,
})
export class DynamicDetail implements AfterViewInit, OnChanges, OnDestroy, OnInit
{
// wee need Dynamic component builder
constructor(
protected typeBuilder: DynamicTypeBuilder,
protected templateBuilder: DynamicTemplateBuilder
) {}
...
Мы только получаем, шаблоны и компоновщики компонентов. Далее следуют свойства, необходимые для нашего примера (подробнее в комментариях)
// reference for a <div> with #dynamicContentPlaceHolder
@ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef})
protected dynamicComponentTarget: ViewContainerRef;
// this will be reference to dynamic content - to be able to destroy it
protected componentRef: ComponentRef<IHaveDynamicData>;
// until ngAfterViewInit, we cannot start (firstly) to process dynamic stuff
protected wasViewInitialized = false;
// example entity ... to be recieved from other app parts
// this is kind of candiate for @Input
protected entity = {
code: "ABC123",
description: "A description of this Entity"
};
В этом простом сценарии наш хостинг-компонент не имеет @Input
. Поэтому он не должен реагировать на изменения. Но, несмотря на этот факт (и чтобы быть готовым к предстоящим изменениям), нам нужно ввести некоторый флаг, если компонент уже (поначалу) был инициирован. И только тогда мы можем начать волшебство.
Наконец, мы будем использовать наш компонентный компоновщик и его просто скомпилированный/кэшированный ComponentFacotry
. Каждому заполнителю Target будет предложено создать Component
с помощью factory.
protected refreshContent(useTextarea: boolean = false){
if (this.componentRef) {
this.componentRef.destroy();
}
// here we get a TEMPLATE with dynamic content === TODO
var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);
// 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 inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
}
Кроме того, нам нужно сохранить ссылку на скомпилированный шаблон.. чтобы иметь возможность destroy()
, когда мы его изменим.
// this is the best moment where to start to process dynamic stuff
public ngAfterViewInit(): void
{
this.wasViewInitialized = true;
this.refreshContent();
}
// wasViewInitialized is an IMPORTANT switch
// when this component would have its own changing @Input()
// - then we have to wait till view is intialized - first OnChange is too soon
public ngOnChanges(changes: {[key: string]: SimpleChange}): void
{
if (this.wasViewInitialized) {
return;
}
this.refreshContent();
}
public ngOnDestroy(){
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
Это в значительной степени. Не забудьте Уничтожить все, что было построено динамически (ngOnDestroy). Кроме того, убедитесь, что кеш динамический types
и modules
, если единственным отличием является их шаблон.
Проверьте все в действии здесь
чтобы увидеть предыдущие версии (например, связанные с RC5) этого сообщения, проверьте историю
directives
ключевое слово в @component
( type.builder.ts
). Разве это не амортизируется после RC5? Если да, есть ли решение, которое позволяет избежать этого? Я думаю о том, чтобы поместить @ngmodule
в app/parts/*.ts
, но пока не удалось ...
EDIT (26/08/2017). Решение ниже хорошо работает с Angular2 и 4. Я обновил его, чтобы он содержал переменную шаблона и обработчик кликов и протестировал его с помощью Angular 4.3.
Для Angular4, ngComponentOutlet, как описано в Ophir answer, является гораздо лучшим решением. Но сейчас пока не поддерживает входы и выходы. Если [этот PR] (https://github.com/angular/angular/pull/15362] принят, это возможно через экземпляр компонента, возвращенный событием create.
ng-dynamic-component может быть лучшим и самым простым решением, но я еще не тестировал его.
@Полевой ответ на поле! Вот еще один (синхронный) пример:
import {Compiler, Component, NgModule, OnInit, ViewChild,
ViewContainerRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
@Component({
selector: 'my-app',
template: `<h1>Dynamic template:</h1>
<div #container></div>`
})
export class App implements OnInit {
@ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
constructor(private compiler: Compiler) {}
ngOnInit() {
this.addComponent(
`<h4 (click)="increaseCounter()">
Click to increase: {{counter}}
`enter code here` </h4>`,
{
counter: 1,
increaseCounter: function () {
this.counter++;
}
}
);
}
private addComponent(template: string, properties?: any = {}) {
@Component({template})
class TemplateComponent {}
@NgModule({declarations: [TemplateComponent]})
class TemplateModule {}
const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = mod.componentFactories.find((comp) =>
comp.componentType === TemplateComponent
);
const component = this.container.createComponent(factory);
Object.assign(component.instance, properties);
// If properties are changed at a later stage, the change detection
// may need to be triggered manually:
// component.changeDetectorRef.detectChanges();
}
}
@NgModule({
imports: [ BrowserModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
Live at http://plnkr.co/edit/fdP9Oc.
ngAfterViewInit
с const template
не будет работать. Но если ваша задача состояла в том, чтобы сократить описанный выше детально описанный подход (создать шаблон, создать компонент, создать модуль, скомпилировать его, создать фабрику .. создать экземпляр) ... вы, вероятно, сделали это
Я, должно быть, приехал на вечеринку поздно, ни одно из решений здесь не показалось мне полезным - слишком беспорядочно и было похоже на слишком много обходного пути.
То, что я закончил, это использовать Angular 4.0.0-beta.6
ngComponentOutlet.
Это дало мне самое кратчайшее, самое простое решение, написанное в файле динамических компонентов.
import {
Component, OnInit, Input, NgModule, NgModuleFactory, Compiler
} from '@angular/core';
@Component({
selector: 'my-component',
template: '<ng-container *ngComponentOutlet="dynamicComponent;
ngModuleFactory: dynamicModule;"></ng-container>',
styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
dynamicComponent;
dynamicModule: NgModuleFactory<any>;
@Input()
text: string;
constructor(private compiler: Compiler) {
}
ngOnInit() {
this.dynamicComponent = this.createNewComponent(this.text);
this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
}
protected createComponentModule (componentType: any) {
@NgModule({
imports: [],
declarations: [
componentType
],
entryComponents: [componentType]
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
protected createNewComponent (text:string) {
let template = 'dynamically created template with text: ${text}';
@Component({
selector: 'dynamic-component',
template: template
})
class DynamicComponent implements OnInit{
text: any;
ngOnInit() {
this.text = text;
}
}
return DynamicComponent;
}
}
my-component
- компонент, в котором выполняется динамический компонентDynamicComponent
- компонент, который будет динамически построен, и он выполняет рендеринг внутри моего компонентаНе забудьте обновить все угловые библиотеки до ^ Angular 4.0.0
Надеюсь это поможет. Удачи!
ОБНОВИТЬ
Также работает для углового 5.
Я решил сжать все, что я узнал, в один файл. Там много чего взять, особенно по сравнению с RC5. Обратите внимание, что этот исходный файл содержит AppModule и AppComponent.
import {
Component, Input, ReflectiveInjector, ViewContainerRef, Compiler, NgModule, ModuleWithComponentFactories,
OnInit, ViewChild
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
@Component({
selector: 'app-dynamic',
template: '<h4>Dynamic Components</h4><br>'
})
export class DynamicComponentRenderer implements OnInit {
factory: ModuleWithComponentFactories<DynamicModule>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }
ngOnInit() {
if (!this.factory) {
const dynamicComponents = {
sayName1: {comp: SayNameComponent, inputs: {name: 'Andrew Wiles'}},
sayAge1: {comp: SayAgeComponent, inputs: {age: 30}},
sayName2: {comp: SayNameComponent, inputs: {name: 'Richard Taylor'}},
sayAge2: {comp: SayAgeComponent, inputs: {age: 25}}};
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
.then((moduleWithComponentFactories: ModuleWithComponentFactories<DynamicModule>) => {
this.factory = moduleWithComponentFactories;
Object.keys(dynamicComponents).forEach(k => {
this.add(dynamicComponents[k]);
})
});
}
}
addNewName(value: string) {
this.add({comp: SayNameComponent, inputs: {name: value}})
}
addNewAge(value: number) {
this.add({comp: SayAgeComponent, inputs: {age: value}})
}
add(comp: any) {
const compFactory = this.factory.componentFactories.find(x => x.componentType === comp.comp);
// If we don't want to hold a reference to the component type, we can also say: const compFactory = this.factory.componentFactories.find(x => x.selector === 'my-component-selector');
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
const cmpRef = this.vcRef.createComponent(compFactory, this.vcRef.length, injector, []);
Object.keys(comp.inputs).forEach(i => cmpRef.instance[i] = comp.inputs[i]);
}
}
@Component({
selector: 'app-age',
template: '<div>My age is {{age}}!</div>'
})
class SayAgeComponent {
@Input() public age: number;
};
@Component({
selector: 'app-name',
template: '<div>My name is {{name}}!</div>'
})
class SayNameComponent {
@Input() public name: string;
};
@NgModule({
imports: [BrowserModule],
declarations: [SayAgeComponent, SayNameComponent]
})
class DynamicModule {}
@Component({
selector: 'app-root',
template: '
<h3>{{message}}</h3>
<app-dynamic #ad></app-dynamic>
<br>
<input #name type="text" placeholder="name">
<button (click)="ad.addNewName(name.value)">Add Name</button>
<br>
<input #age type="number" placeholder="age">
<button (click)="ad.addNewAge(age.value)">Add Age</button>
',
})
export class AppComponent {
message = 'this is app component';
@ViewChild(DynamicComponentRenderer) dcr;
}
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent, DynamicComponentRenderer],
bootstrap: [AppComponent]
})
export class AppModule {}'
У меня есть простой пример, чтобы показать, как сделать динамический компонент angular 2 rc6.
Скажем, у вас есть динамический шаблон html = template1 и вы хотите динамическую загрузку, сначала вставьте в компонент
@Component({template: template1})
class DynamicComponent {}
здесь template1 как html, может содержать компонент ng2
Из rc6 необходимо, чтобы @NgModule обертывал этот компонент. @NgModule, как и модуль в anglarJS 1, он отделяет разную часть приложения ng2, поэтому:
@Component({
template: template1,
})
class DynamicComponent {
}
@NgModule({
imports: [BrowserModule,RouterModule],
declarations: [DynamicComponent]
})
class DynamicModule { }
(Здесь import RouterModule, так как в моем примере в моем html есть некоторые компоненты маршрута, как вы можете видеть позже)
Теперь вы можете скомпилировать DynamicModule как: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then(
factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))
И мы должны поставить выше в app.moudule.ts, чтобы загрузить его, см. мой app.moudle.ts. Для получения дополнительной и полной информации проверьте: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts и app.moudle.ts
и см. демонстрацию: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview
Решено это в Angular 2 Final, просто с помощью директивы dynamicComponent из ng-dynamic.
Использование:
<div *dynamicComponent="template; context: {text: text};"></div>
Где шаблон - это ваш динамический шаблон, а контекст может быть установлен для любой динамической модели данных, с которой вы хотите связать свой шаблон.
Следя за отличным ответом Radmin, требуется небольшая настройка для всех, кто использует angular -cli версии 1.0.0-beta.22 и выше.
COMPILER_PROVIDERS
больше нельзя импортировать (подробности см. angular -cli GitHub).
Итак, обходной путь состоит в том, чтобы не использовать COMPILER_PROVIDERS
и JitCompiler
в разделе providers
вообще, но вместо этого используйте JitCompilerFactory
from '@angular/compiler', как это происходит внутри класса построителя типов:
private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();
Как вы можете видеть, он не инъектируется и, следовательно, не имеет зависимостей с DI. Это решение также должно работать для проектов, не использующих angular -cli.
Я хочу добавить несколько деталей поверх этой очень хорошей публикации Radim.
Я принял это решение и немного поработал над ним и быстро столкнулся с некоторыми ограничениями. Я просто опишу их, а затем даю решение этому.
Я задал еще один вопрос, основанный на этом сообщении, о том, как достичь этих ограничений, которые можно найти здесь:
рекурсивная динамическая компоновка шаблонов в angular2
Я просто изложил ответы на эти ограничения, если вы столкнулись с той же проблемой, что и я, поскольку это делает решение более гибким. Было бы замечательно, если бы начальный плункер также был обновлен с этим.
Чтобы включить динамическую детализацию внутри друг друга, вам нужно добавить DynamicModule.forRoot() в инструкции import в type.builder.ts
protected createComponentModule (componentType: any) {
@NgModule({
imports: [
PartsModule,
DynamicModule.forRoot() //this line here
],
declarations: [
componentType
],
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
Кроме того, было невозможно использовать <dynamic-detail>
внутри одной из частей, являющейся строковым редактором или текстовым редактором.
Чтобы включить это, вам нужно будет изменить parts.module.ts
и dynamic.module.ts
Внутри parts.module.ts
Вам нужно добавить DynamicDetail
в DYNAMIC_DIRECTIVES
export const DYNAMIC_DIRECTIVES = [
forwardRef(() => StringEditor),
forwardRef(() => TextEditor),
DynamicDetail
];
Также в dynamic.module.ts
вам придется удалить dynamicDetail, поскольку они теперь являются частью частей
@NgModule({
imports: [ PartsModule ],
exports: [ PartsModule],
})
Работая модифицированный плункер можно найти здесь: http://plnkr.co/edit/UYnQHF?p=preview (я не решил эту проблему, я просто посланник:-D)
Наконец, было невозможно использовать шаблоны в частях, созданных на динамических компонентах. Решение (или обходной путь. Im не уверен, что его ошибка angular или неправильное использование фреймворка) заключалась в создании компилятора в конструкторе вместо его инъекции.
private _compiler;
constructor(protected compiler: RuntimeCompiler) {
const compilerFactory : CompilerFactory =
platformBrowserDynamic().injector.get(CompilerFactory);
this._compiler = compilerFactory.createCompiler([]);
}
Затем используйте _compiler
для компиляции, затем также активируются шаблоны.
return new Promise((resolve) => {
this._compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories) =>
{
let _ = window["_"];
factory = _.find(moduleWithFactories.componentFactories, { componentType: type });
this._cacheOfFactories[template] = factory;
resolve(factory);
});
});
Надеюсь, это поможет кому-то еще!
С уважением Morten
В Angular 7.x я использовал Angular-элементы для этого.
Установите @angular-elements npm я @angular/elements -s
Создать вспомогательный сервис.
import { Injectable, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { IStringAnyMap } from 'src/app/core/models';
import { AppUserIconComponent } from 'src/app/shared';
const COMPONENTS = {
'user-icon': AppUserIconComponent
};
@Injectable({
providedIn: 'root'
})
export class DynamicComponentsService {
constructor(private injector: Injector) {
}
public register(): void {
Object.entries(COMPONENTS).forEach(([key, component]: [string, any]) => {
const CustomElement = createCustomElement(component, { injector: this.injector });
customElements.define(key, CustomElement);
});
}
public create(tagName: string, data: IStringAnyMap = {}): HTMLElement {
const customEl = document.createElement(tagName);
Object.entries(data).forEach(([key, value]: [string, any]) => {
customEl[key] = value;
});
return customEl;
}
}
Обратите внимание, что ваш пользовательский тег элемента должен отличаться от углового селектора компонента. в AppUserIconComponent:
...
selector: app-user-icon
...
и в этом случае имя пользовательского тега я использовал "user-icon".
@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(
dynamicComponents: DynamicComponentsService,
) {
dynamicComponents.register();
}
}
dynamicComponents.create('user-icon', {user:{...}});
или вот так:
const html = '<div class="wrapper"><user-icon class="user-icon" user='${JSON.stringify(rec.user)}'></user-icon></div>';
this.content = this.domSanitizer.bypassSecurityTrustHtml(html);
(в шаблоне):
<div class="comment-item d-flex" [innerHTML]="content"></div>
Обратите внимание, что во втором случае вы должны передать объекты с помощью JSON.stringify и после этого снова проанализировать его. Я не могу найти лучшего решения.
Я сам пытаюсь понять, как я могу обновить RC4 до RC5, и поэтому я наткнулся на эту запись, и новый подход к созданию динамических компонентов по-прежнему остается для меня загадкой, поэтому я не предлагаю ничего на компоненте factory resolver.
Но я могу предложить немного более четкий подход к созданию компонентов в этом сценарии - просто используйте переключатель в шаблоне, который будет создавать редактор строк или текстовый редактор в соответствии с некоторым условием, например:
<form [ngSwitch]="useTextarea">
<string-editor *ngSwitchCase="false" propertyName="'code'"
[entity]="entity"></string-editor>
<text-editor *ngSwitchCase="true" propertyName="'code'"
[entity]="entity"></text-editor>
</form>
И, между прочим, "[" в [prop] выражении имеет значение, это указывает на одностороннее связывание данных, поэтому вы можете и даже должны опустить их в случае, если вы знаете, что вам не нужно связывать свойство с переменной.
switch
/ case
содержит несколько решений. Но представьте, что сгенерированный шаблон может быть очень большим ... и отличаться для каждой сущности, различаться по безопасности, различаться по статусу сущности, по каждому типу свойства (число, дата, ссылка ... редакторы) ... В таком случае, решение этого в html-шаблоне с помощью ngSwitch
создаст большой, очень-очень большой html
файл.
2019 февраль ответ
Отличные новости! Похоже, что пакет @angular/cdk теперь имеет первоклассную поддержку порталов !
1) Добавить те компоненты, которые вы хотите, чтобы динамически добавлять в entryComponents
массив вашего AppModule
.
2)... и в вашем компоненте:
import { Component, OnInit } from '@angular/core';
import { ComponentPortal } from '@angular/cdk/portal';
@Component({
selector: 'my-app',
template: '
<h3>Portal Example</h3>
<button (click)="onClickAddChild()">Click to add component dynamically</button>
<ng-template [cdkPortalOutlet]="myPortal"></ng-template>
'
})
export class AppComponent {
myPortal;
onClickAddChild() {
this.myPortal = new ComponentPortal(ChildComponent);
}
}
@Component({
selector: 'my-child',
template: '<p>I am a dynamic component!</p>'
})
export class ChildComponent {
}
Это пример динамических элементов управления формы, сгенерированных с сервера.
https://stackblitz.com/edit/angular-t3mmg6
Этот пример динамических элементов управления Form находится в компоненте add (здесь вы можете получить Formcontrols с сервера). Если вы видите метод addcomponent, вы можете увидеть элементы управления Forms. В этом примере я не использую угловой материал, но он работает (я использую @work). Это цель для угловых 6, но работает во всех предыдущих версиях.
Нужно добавить JITComplierFactory для AngularVersion 5 и выше.
Спасибо
Виджай
Основываясь на ответе Ophir Stern, вот вариант, который работает с AoT в Angular 4. Единственная проблема, с которой я сталкиваюсь, это то, что я не могу вводить какие-либо сервисы в DynamicComponent, но я могу жить с этим.
Примечание. Я не тестировал с помощью Angular 5.
import { Component, OnInit, Input, NgModule, NgModuleFactory, Compiler, EventEmitter, Output } from '@angular/core';
import { JitCompilerFactory } from '@angular/compiler';
export function createJitCompiler() {
return new JitCompilerFactory([{
useDebug: false,
useJit: true
}]).createCompiler();
}
type Bindings = {
[key: string]: any;
};
@Component({
selector: 'app-compile',
template: '
<div *ngIf="dynamicComponent && dynamicModule">
<ng-container *ngComponentOutlet="dynamicComponent; ngModuleFactory: dynamicModule;">
</ng-container>
</div>
',
styleUrls: ['./compile.component.scss'],
providers: [{provide: Compiler, useFactory: createJitCompiler}]
})
export class CompileComponent implements OnInit {
public dynamicComponent: any;
public dynamicModule: NgModuleFactory<any>;
@Input()
public bindings: Bindings = {};
@Input()
public template: string = '';
constructor(private compiler: Compiler) { }
public ngOnInit() {
try {
this.loadDynamicContent();
} catch (err) {
console.log('Error during template parsing: ', err);
}
}
private loadDynamicContent(): void {
this.dynamicComponent = this.createNewComponent(this.template, this.bindings);
this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
}
private createComponentModule(componentType: any): any {
const runtimeComponentModule = NgModule({
imports: [],
declarations: [
componentType
],
entryComponents: [componentType]
})(class RuntimeComponentModule { });
return runtimeComponentModule;
}
private createNewComponent(template: string, bindings: Bindings): any {
const dynamicComponent = Component({
selector: 'app-dynamic-component',
template: template
})(class DynamicComponent implements OnInit {
public bindings: Bindings;
constructor() { }
public ngOnInit() {
this.bindings = bindings;
}
});
return dynamicComponent;
}
}
Надеюсь это поможет.
Ура!