편집 -2.3.0 관련 (2016-12-07)
참고 : 이전 버전에 대한 솔루션을 얻으려면이 게시물의 기록을 확인하십시오.
비슷한 주제가 여기에서 논의됩니다 . Angular 2의 $ compile과 동일 합니다. 우리는 사용해야 JitCompiler
하고 NgModule
. NgModule
Angular2 에 대한 자세한 내용 은 여기를 참조하십시오.
간단히 말해서
가 작동 plunker / 예 (동적 템플릿 동적 성분계 동적 모듈, JitCompiler
... 액션)
교장은이다 :
1) 생성 템플릿
2) 찾을 수 ComponentFactory
캐시에 - 로 이동 7)
3) - 작성 Component
4) - 생성 Module
5) - 컴파일 Module
6) - 리턴 (나중에 사용할 수 있도록 캐시) ComponentFactory
7) 사용 대상 및 ComponentFactory
인스턴스를 생성 역동적 인Component
여기에 코드 스 니펫이 있습니다 ( 여기 더 있습니다 ) -우리의 커스텀 빌더는 빌드 / 캐시를 반환 ComponentFactory
하고 타겟 플레이스 홀더가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's inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
이것은 간단합니다. 자세한 내용은 아래를 참조하십시오.
.
TL & DR
일부 스 니펫에 추가 설명이 필요한 경우 플런저를 관찰하고 세부 정보를 다시 읽으십시오.
.
자세한 설명-Angular2 RC6 ++ 및 런타임 구성 요소
의 설명 아래 이 경우 , 우리는 것입니다
- 모듈 만들기
PartsModule:NgModule
(작은 조각 보유자)
DynamicModule:NgModule
동적 구성 요소를 포함하는 다른 모듈을 작성하십시오 ( 동적 참조 PartsModule
).
- 동적 템플릿 생성 (간단한 접근)
- 새
Component
유형 만들기 (템플릿이 변경된 경우에만)
- 새로 만듭니다
RuntimeModule:NgModule
. 이 모듈에는 이전에 생성 된 Component
유형 이 포함됩니다
- 전화
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
하다ComponentFactory
DynamicComponent
View Target 자리 표시 자의 작업 인스턴스를 생성 하고ComponentFactory
- 지정
@Inputs
에 새로운 인스턴스 (에서 스위치 INPUT
로 TEXTAREA
편집) , 소비@Outputs
NgModule
우리는 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.module.ts을
두 번째는 Dynamic stuff 처리를위한 모듈입니다. 호스팅 구성 요소와 일부 공급자가 포함됩니다. 이를 위해 표준 방식으로 게시합니다.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
마지막으로 임시 런타임 모듈이 필요하지만 나중에 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 - 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)은 다음에 만들 동적 구성 요소의 일부 여야합니다.
좀 더 쉽게 만들기 위해 인터페이스를 사용하여 템플릿 빌더가 사용할 수있는 속성을 정의 할 수 있습니다. 이는 동적 컴포넌트 유형으로 구현됩니다.
export interface IHaveDynamicData {
public entity: any;
...
}
ComponentFactory
빌더
여기서 매우 중요한 것은 명심해야합니다.
우리와 함께 빌드 한 컴포넌트 유형 DynamicTypeBuilder
은 템플릿에 따라 다를 수 있습니다 (위에서 생성) . 구성 요소의 특성 (입력, 출력 또는 일부 보호)은 여전히 동일합니다. 다른 속성이 필요한 경우 템플릿과 유형 작성기의 다른 조합을 정의해야합니다
따라서 우리는 솔루션의 핵심을 감동시키고 있습니다. 빌더는 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'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);
});
});
}
우리가 만들고 위의 캐시 모두 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 문을 요약 해 봅시다 :
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
합니다. 우리의 목표 자리는 인스턴스화하게됩니다 을Component
그 공장.
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;
//...
});
}
작은 확장
또한 컴파일 된 템플릿에 대한 참조를 유지해야 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 관련) 을 보려면 기록을 확인하십시오.