EDIT - liên quan đến 2.3.0 (2016-12-07)
LƯU Ý: để có giải pháp cho phiên bản trước, hãy kiểm tra lịch sử của bài đăng này
Chủ đề tương tự được thảo luận ở đây Tương đương với $ compile trong Angular 2 . Chúng ta cần sử dụng JitCompiler
và NgModule
. Tìm hiểu thêm về NgModule
Angular2 tại đây:
Tóm lại
Có một plunker / ví dụ làm việc (mẫu động, loại thành phần động, mô đun động,JitCompiler
, ... đang hoạt động)
Hiệu trưởng là:
1) tạo Mẫu
2) tìm ComponentFactory
trong bộ đệm - chuyển đến 7)
3) - tạo Component
4) - tạo Module
5) - biên dịch Module
6) - trả lại (và bộ đệm để sử dụng sau) ComponentFactory
7) sử dụng Target vàComponentFactory
để tạo Instance năng độngComponent
Đây là một đoạn mã (nhiều hơn ở đây ) - Trình tạo tùy chỉnh của chúng tôi vừa trở lại được xây dựng / lưu vào bộ đệm ComponentFactory
và chế độ xem Giữ chỗ mục tiêu tiêu thụ để tạo một phiên bản củaDynamicComponent
// 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's inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
Đây là nó - tóm lại là vậy. Để biết thêm chi tiết .. đọc bên dưới
.
TL & DR
Quan sát một plunker và quay lại để đọc chi tiết trong trường hợp một số đoạn trích yêu cầu giải thích thêm
.
Giải thích chi tiết - Angular2 RC6 ++ và các thành phần thời gian chạy
Dưới đây mô tả kịch bản này , chúng tôi sẽ
- tạo một mô-đun
PartsModule:NgModule
(người giữ các mảnh nhỏ)
- tạo một mô-đun khác
DynamicModule:NgModule
, sẽ chứa thành phần động của chúng tôi (và tham chiếu PartsModule
động)
- tạo Mẫu động (cách tiếp cận đơn giản)
- tạo
Component
kiểu mới (chỉ khi mẫu đã thay đổi)
- tạo mới
RuntimeModule:NgModule
. Mô-đun này sẽ chứa Component
loại được tạo trước đó
- gọi
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
để nhậnComponentFactory
- tạo một thực thể
DynamicComponent
- công việc của trình giữ chỗ View Target vàComponentFactory
- gán
@Inputs
cho trường hợp mới (chuyển đổi từ INPUT
để TEXTAREA
chỉnh sửa) , tiêu thụ@Outputs
NgModule
Chúng tôi cần một NgModule
s.
Mặc dù tôi muốn đưa ra một ví dụ rất đơn giản, nhưng trong trường hợp này, tôi sẽ cần ba mô-đun (thực tế là 4 - nhưng tôi không tính AppModule) . Xin vui lòng, lấy điều này thay vì một đoạn đơn giản làm cơ sở cho một trình tạo thành phần động thực sự vững chắc.
Sẽ có một mô-đun cho tất cả các thành phần nhỏ, ví dụ như string-editor
, text-editor
( date-editor
, number-editor
...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Nơi DYNAMIC_DIRECTIVES
có thể mở rộng và được dự định để giữ tất cả các phần nhỏ được sử dụng cho mẫu / loại Thành phần động của chúng tôi. Kiểm tra ứng dụng / bộ phận / bộ phận.module.ts
Thứ hai sẽ là mô-đun để xử lý công cụ năng động của chúng tôi. Nó sẽ chứa các thành phần lưu trữ và một số nhà cung cấp .. sẽ là singletons. Do đó, chúng tôi sẽ xuất bản chúng theo cách tiêu chuẩn - vớiforRoot()
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
],
};
}
}
Kiểm tra việc sử dụng forRoot()
trongAppModule
Cuối cùng, chúng ta sẽ cần một mô-đun adhoc, thời gian chạy .. nhưng điều đó sẽ được tạo sau này, như là một phần của DynamicTypeBuilder
công việc.
Mô-đun thứ tư, mô-đun ứng dụng, là người giữ các nhà cung cấp trình biên dịch khai báo:
...
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
],
Đọc (đọc) nhiều hơn về NgModule ở đó:
Một mẫu builder
Trong ví dụ của chúng tôi, chúng tôi sẽ xử lý chi tiết về loại thực thể này
entity = {
code: "ABC123",
description: "A description of this Entity"
};
Để tạo một template
, trong plunker này, chúng tôi sử dụng trình xây dựng đơn giản / ngây thơ này.
Giải pháp thực sự, một trình xây dựng mẫu thực sự, là nơi ứng dụng của bạn có thể làm được rất nhiều
// 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>";
}
}
Một mẹo ở đây là - nó xây dựng một mẫu sử dụng một số thuộc tính đã biết, vd entity
. Thuộc tính (-ies) đó phải là một phần của thành phần động, mà chúng ta sẽ tạo tiếp theo.
Để làm cho nó dễ dàng hơn một chút, chúng ta có thể sử dụng một giao diện để xác định các thuộc tính mà trình tạo Mẫu của chúng ta có thể sử dụng. Điều này sẽ được thực hiện bởi loại Thành phần động của chúng tôi.
export interface IHaveDynamicData {
public entity: any;
...
}
Một ComponentFactory
người xây dựng
Điều rất quan trọng ở đây là ghi nhớ:
loại thành phần của chúng tôi, xây dựng với chúng tôi DynamicTypeBuilder
, có thể khác nhau - nhưng chỉ bởi mẫu của nó (được tạo ở trên) . Các thuộc tính của thành phần (đầu vào, đầu ra hoặc một số được bảo vệ) vẫn giống nhau. Nếu chúng ta cần các thuộc tính khác nhau, chúng ta nên xác định kết hợp khác nhau của Trình tạo mẫu và Kiểu
Vì vậy, chúng tôi đang chạm vào cốt lõi của giải pháp của chúng tôi. Builder, sẽ 1) tạo ComponentType
2) tạo bộ đệmNgModule
3) biên dịch ComponentFactory
4) để sử dụng lại sau này.
Một phụ thuộc chúng ta cần nhận:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
Và đây là đoạn trích làm thế nào để có được 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'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);
});
});
}
Ở trên chúng tôi tạo và lưu trữ cả hai Component
và Module
. Bởi vì nếu mẫu (thực tế là phần động thực sự của tất cả) giống nhau .. chúng ta có thể sử dụng lại
Và đây là hai phương thức, đại diện cho cách thực sự tuyệt vời để tạo một lớp / kiểu trang trí trong thời gian chạy. Không chỉ @Component
mà còn@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;
}
Quan trọng:
các loại động thành phần của chúng tôi khác nhau, nhưng chỉ bằng mẫu. Vì vậy, chúng tôi sử dụng thực tế đó để lưu trữ chúng. Điều này thực sự rất quan trọng. Angular2 cũng sẽ lưu trữ những cái này .. theo loại . Và nếu chúng ta tạo lại cho cùng một chuỗi các kiểu mẫu mới ... chúng ta sẽ bắt đầu tạo ra rò rỉ bộ nhớ.
ComponentFactory
được sử dụng bởi thành phần lưu trữ
Phần cuối cùng là một thành phần, lưu trữ mục tiêu cho thành phần động của chúng ta, ví dụ <div #dynamicContentPlaceHolder></div>
. Chúng tôi có một tham chiếu đến nó và sử dụng ComponentFactory
để tạo ra một thành phần. Đó là một cách ngắn gọn, và đây là tất cả các phần của thành phần đó (nếu cần, mở plunker ở đây )
Trước tiên hãy tóm tắt báo cáo nhập khẩu:
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
) {}
...
Chúng tôi chỉ nhận, xây dựng mẫu và thành phần. Tiếp theo là các thuộc tính cần thiết cho ví dụ của chúng tôi (thêm ý kiến)
// 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"
};
Trong kịch bản đơn giản này, thành phần lưu trữ của chúng tôi không có bất kỳ @Input
. Vì vậy, nó không phải phản ứng với những thay đổi. Nhưng bất chấp thực tế đó (và để sẵn sàng cho những thay đổi sắp tới) - chúng tôi cần giới thiệu một số cờ nếu thành phần đã có (trước tiên) . Và chỉ sau đó chúng ta có thể bắt đầu phép thuật.
Cuối cùng, chúng tôi sẽ sử dụng trình xây dựng thành phần của chúng tôi và nó chỉ được biên dịch / lưu trữ ComponentFacotry
. Chúng tôi giữ chỗ Target sẽ được yêu cầu phải nhanh chóng cácComponent
với nhà máy đó.
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's inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
}
mở rộng nhỏ
Ngoài ra, chúng ta cần giữ một tham chiếu đến mẫu đã biên dịch .. để có thể thực hiện đúng destroy()
, bất cứ khi nào chúng ta sẽ thay đổi nó.
// 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;
}
}
làm xong
Đó là khá nhiều đó. Đừng quên phá hủy bất cứ thứ gì được xây dựng một cách linh hoạt (ngOnDestroy) . Ngoài ra, hãy chắc chắn để lưu trữ động types
và modules
nếu sự khác biệt duy nhất là mẫu của họ.
Kiểm tra tất cả trong hành động ở đây
để xem các phiên bản trước (ví dụ RC5 liên quan) của bài đăng này, hãy kiểm tra lịch sử