我想动态创建一个模板。这应该用于在运行时构建ComponentType,并将其放置(甚至替换)到宿主组件内部的某个位置。
直到RC4我使用ComponentResolver,但与RC5我得到以下消息:
ComponentResolver is deprecated for dynamic compilation.
Use ComponentFactoryResolver together with @NgModule/@Component.entryComponents or ANALYZE_FOR_ENTRY_COMPONENTS provider instead.
For runtime compile only, you can also use Compiler.compileComponentSync/Async.
我找到了这个文档(Angular 2同步动态组件创建)
你要明白我可以用任何一种
一种带有ComponentFactoryResolver的动态ngIf。如果我在@Component({entryComponents: [comp1, comp2],…})内部传递已知的组件-我可以使用.resolveComponentFactory(componentToRender);
真正的运行时编译,使用编译器…
但问题是如何使用编译器?上面的说明说,我应该调用:Compiler.compileComponentSync/Async -那么如何?
为例。我想为一种设置创建(基于一些配置条件)这种模板
<form>
<string-editor
[propertyName]="'code'"
[entity]="entity"
></string-editor>
<string-editor
[propertyName]="'description'"
[entity]="entity"
></string-editor>
...
在另一种情况下,这个(字符串编辑器被文本编辑器取代)
<form>
<text-editor
[propertyName]="'code'"
[entity]="entity"
></text-editor>
...
等等(根据属性类型设置不同的数字/日期/引用编辑器,为某些用户跳过一些属性……)例如,这是一个例子,实际的配置可以生成更多不同和复杂的模板。
模板正在改变,所以我不能使用ComponentFactoryResolver和传递现有的…我需要一个解决方案与编译器。
我想在Radim的这篇非常出色的文章上添加一些细节。
我采用了这个解决方案并研究了一段时间,很快就遇到了一些限制。我只列出这些,然后给出答案。
首先,我无法渲染动态细节在一个
dynamic-detail(基本上是将动态ui嵌套在彼此内部)。
下一个问题是我想在里面渲染一个动态细节
解决方案中提供的部件之一。这是
初始解也是不可能的。
最后,它不可能在动态部分使用模板url,如字符串编辑器。
我在这篇文章的基础上提出了另一个问题,关于如何实现这些限制,可以在这里找到:
在angar2中递归动态模板编译
如果您遇到与我相同的问题,我将概述这些限制的答案,因为这使解决方案更加灵活。这将是了不起的有最初的活塞更新,以及。
为了在彼此内部嵌套dynamic-detail,你需要在type.builder.ts的import语句中添加DynamicModule.forRoot()
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
你需要在DYNAMIC_DIRECTIVES中添加DynamicDetail
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)
最后,不可能在动态组件上创建的部件中使用templateurls。解决方案(或变通方案)。我不确定这是一个angular错误还是框架的错误使用)是在构造函数中创建一个编译器,而不是注入它。
private _compiler;
constructor(protected compiler: RuntimeCompiler) {
const compilerFactory : CompilerFactory =
platformBrowserDynamic().injector.get(CompilerFactory);
this._compiler = compilerFactory.createCompiler([]);
}
然后使用_compiler进行编译,然后也启用templateUrls。
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
EDIT (26/08/2017): The solution below works well with Angular2 and 4. I've updated it to contain a template variable and click handler and tested it with Angular 4.3.
For Angular4, ngComponentOutlet as described in Ophir's answer is a much better solution. But right now it does not support inputs & outputs yet. If [this PR](https://github.com/angular/angular/pull/15362] is accepted, it would be possible through the component instance returned by the create event.
ng-dynamic-component may be the best and simplest solution altogether, but I haven't tested that yet.
@Long Field的答案是正确的!下面是另一个(同步)例子:
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 {}
请访问http://plnkr.co/edit/fdP9Oc。
在角7。我使用了角元素。
安装@angular-elements
NPM I @angular/elements
创建配件服务。
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;
}
}
注意,你的自定义元素标签必须与angular组件选择器不同。
在AppUserIconComponent:
...
selector: app-user-icon
...
在这种情况下,自定义标签名称我使用“user-icon”。
然后你必须在AppComponent中调用register:
@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,然后再次解析它。我找不到更好的解决办法了。
我想在Radim的这篇非常出色的文章上添加一些细节。
我采用了这个解决方案并研究了一段时间,很快就遇到了一些限制。我只列出这些,然后给出答案。
首先,我无法渲染动态细节在一个
dynamic-detail(基本上是将动态ui嵌套在彼此内部)。
下一个问题是我想在里面渲染一个动态细节
解决方案中提供的部件之一。这是
初始解也是不可能的。
最后,它不可能在动态部分使用模板url,如字符串编辑器。
我在这篇文章的基础上提出了另一个问题,关于如何实现这些限制,可以在这里找到:
在angar2中递归动态模板编译
如果您遇到与我相同的问题,我将概述这些限制的答案,因为这使解决方案更加灵活。这将是了不起的有最初的活塞更新,以及。
为了在彼此内部嵌套dynamic-detail,你需要在type.builder.ts的import语句中添加DynamicModule.forRoot()
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
你需要在DYNAMIC_DIRECTIVES中添加DynamicDetail
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)
最后,不可能在动态组件上创建的部件中使用templateurls。解决方案(或变通方案)。我不确定这是一个angular错误还是框架的错误使用)是在构造函数中创建一个编译器,而不是注入它。
private _compiler;
constructor(protected compiler: RuntimeCompiler) {
const compilerFactory : CompilerFactory =
platformBrowserDynamic().injector.get(CompilerFactory);
this._compiler = compilerFactory.createCompiler([]);
}
然后使用_compiler进行编译,然后也启用templateUrls。
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
在Ophir Stern的答案基础上,这里有一个与Angular 4中的AoT一起工作的变体。唯一的问题是我不能向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;
}
}
希望这能有所帮助。
干杯!