동적 템플릿을 사용하여 Angular 2.0으로 동적 구성 요소를 컴파일하는 방법은 무엇입니까?


197

템플릿을 동적으로 만들고 싶습니다. 이것은을 구축하는 데 사용되어야 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 Synchronous Dynamic Component Creation )

그리고 어느 쪽이든 사용할 수 있다는 것을 이해하십시오

  • 동적의 종류 ngIfComponentFactoryResolver. 내부에 알려진 구성 요소를 전달하면 @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. 의 솔루션이 필요합니다 .


내가 찾은 해결책이 너무 좋았 기 때문에이 질문을 찾는 모든 사람이 내 대답을 살펴보고 싶었습니다. :)
Richard Houltz


여기에 모든 단일 답변에 대한 문제와 $compile실제로 이러한 방법으로 할 수없는 작업이 있습니다. 제 3 자 페이지와 ajax 호출을 통해 HTML을 컴파일하려는 응용 프로그램을 만들고 있습니다. 페이지에서 HTML을 제거하여 내 템플릿에 넣을 수 없습니다. 한숨
Augie 가드너에게

@AugieGardner 설계 상으로는 이것이 불가능한 이유가 있습니다. Angular는 잘못된 아키텍처 결정이나 일부 사람들이 가지고있는 레거시 시스템으로 인해 잘못이 아닙니다. 기존 HTML 코드를 구문 분석하려면 Angular가 WebComponents와 완벽하게 작동하므로 다른 프레임 워크를 자유롭게 사용할 수 있습니다. 경험이 부족한 프로그래머의 무리를 이끌 명확한 경계를 설정하는 것은 레거시 시스템이 거의없는 더러운 해킹을 허용하는 것보다 중요합니다.
Phil

답변:


163

편집 -2.3.0 관련 (2016-12-07)

참고 : 이전 버전에 대한 솔루션을 얻으려면이 게시물의 기록을 확인하십시오.

비슷한 주제가 여기에서 논의됩니다 . Angular 2의 $ compile과 동일 합니다. 우리는 사용해야 JitCompiler하고 NgModule. NgModuleAngular2 에 대한 자세한 내용 은 여기를 참조하십시오.

간단히 말해서

작동 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 ++ 및 런타임 구성 요소

의 설명 아래 이 경우 , 우리는 것입니다

  1. 모듈 만들기 PartsModule:NgModule (작은 조각 보유자)
  2. DynamicModule:NgModule동적 구성 요소를 포함하는 다른 모듈을 작성하십시오 ( 동적 참조 PartsModule).
  3. 동적 템플릿 생성 (간단한 접근)
  4. Component유형 만들기 (템플릿이 변경된 경우에만)
  5. 새로 만듭니다 RuntimeModule:NgModule. 이 모듈에는 이전에 생성 된 Component유형 이 포함됩니다
  6. 전화 JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)하다ComponentFactory
  7. DynamicComponentView Target 자리 표시 자의 작업 인스턴스를 생성 하고ComponentFactory
  8. 지정 @Inputs새로운 인스턴스 (에서 스위치 INPUTTEXTAREA편집) , 소비@Outputs

NgModule

우리는 NgModules 가 필요합니다 .

매우 간단한 예제를 보여주고 싶지만이 경우에는 세 개의 모듈이 필요합니다 (사실 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) 작성 ComponentType2) 작성 NgModule3) 컴파일 ComponentFactory4) 나중에 재사용 할 수 있도록 캐시 합니다.

우리가 받아야 할 의존성 :

// 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);
            });
    });
}

우리가 만들고 위의 캐시 모두 ComponentModule. 템플릿 (실제로이 모든 것의 실제 동적 부분) 이 동일하면 재사용 할 수 있습니다.

다음은 런타임에 데코 레이팅 된 클래스 / 유형 을 만드는 방법을 나타내는 멋진 두 가지 방법 입니다. 뿐만 아니라 @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) . 또한, 반드시 캐시의 동적 typesmodules유일한 차이점은 템플릿의 경우.

여기 에서 모두 확인 하십시오

이 게시물의 이전 버전 (예 : RC5 관련) 을 보려면 기록을 확인하십시오.


50
이처럼 복잡한 솔루션처럼 보이지 않습니다. 더 이상 사용되지 않는 솔루션은 매우 간단하고 명확했습니다. 다른 방법이 있습니까?
tibbus

3
@tibbus와 같은 방식으로 생각합니다. 더 이상 사용되지 않는 코드에서보다 복잡합니다. 그래도 답변 주셔서 감사합니다.
Lucio Mollinedo

5
@ribsies 참고해 주셔서 감사합니다. 무언가를 명확히 해 드리겠습니다. 다른 많은 답변들이 그것을 간단 하게 만들려고 노력합니다 . 그러나 나는 그것을 설명하고 실제 사용에 닫힌 시나리오에서 보여 주려고합니다 . 우리는 물건을 캐싱해야하고, 재창조 등을 위해 destroy를 호출해야 할 것입니다. 따라서 역동적 인 건물의 마술은 type.builder.ts당신이 지적한 것처럼 실제로 모든 사용자가 그 모든 것을 어떻게 넣을 지 이해할 것입니다. 문맥 ... 그것이 유용 할 수 있기를 바랍니다;)
Radim Köhler

7
@Radim Köhler-이 예제를 시도했습니다. AOT없이 작동합니다. 그러나 AOT 로이 작업을 실행하려고하면 "런타임 구성 요소 모듈에 대한 NgModule 메타 데이터가 없습니다"라는 오류가 표시됩니다. 이 오류를 해결하도록 도와주십시오.
Trusha

4
답 자체가 완벽합니다! 그러나 실제 응용 프로그램에는 실용적이지 않습니다. 각 팀은 비즈니스 응용 프로그램에서 일반적으로 요구되는 사항이므로 프레임 워크에서이를위한 솔루션을 제공해야합니다. 그렇지 않은 경우 Angular 2가 비즈니스 응용 프로그램에 적합한 플랫폼인지 묻습니다.
Karl

58

편집 (26/08/2017) : 아래 솔루션은 Angular2 및 4에서 잘 작동합니다. 템플릿 변수를 포함하도록 업데이트하고 클릭 처리기를 Angular 4.3으로 테스트했습니다.
Angular4의 경우 Ophir의 답변에 설명 된 ngComponentOutlet 이 훨씬 더 나은 솔루션입니다. 그러나 지금 아직 입력 및 출력을 지원하지 않습니다 . [this PR] ( https://github.com/angular/angular/pull/15362] 를 수락하면 create 이벤트에서 반환 한 구성 요소 인스턴스를 통해 가능합니다.
ng-dynamic-component 가 가장 간단하고 간단 할 수 있습니다. 솔루션을 모두 테스트했지만 아직 테스트하지 않았습니다.

@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거주하십시오 .


3
나는 그것을 작성하는 방법의 예입니다,라고 말하고 싶지만 가능한 한 적은 코드로 할 수있는 내 답변에서와 같은 stackoverflow.com/a/38888009/1679310가 . 조건이 변경 될 때 유용한 경우 (대부분 RE 생성 템플릿) 가되어야하는 경우 에는 간단한 ngAfterViewInit호출이 const template작동하지 않습니다. 당신의 작업은 자세히 설명한 방법을 줄일 수 있다면 그러나 (인스턴스를 생성 .. 팩토리를 작성, 컴파일, 모듈을 생성, 구성 요소를 생성, 템플릿을 생성) ... 당신은 아마 한
Radim 쾰러을

솔루션 주셔서 감사합니다 : templateUrl 및 스타일을로드하는 데 문제가 있지만 다음과 같은 오류가 발생합니다. No ResourceLoader 구현이 제공되지 않았습니다. URL localhost : 3000 / app / pages / pages_common.css를 읽을 수 없습니다 .
Gerardlamo

컨트롤과 같은 그리드의 셀에 특정한 데이터로 HTML 템플릿을 컴파일 할 수 있습니까? plnkr.co/edit/vJHUCnsJB7cwNJr2cCwp?p=preview 이 플런저에서 어떻게 컴파일하고 마지막 열에 이미지를 표시 할 수 있습니까? 도움이 필요하세요?
Karthick

1
@monnef, 당신이 맞아요. 콘솔 로그를 확인하지 않았습니다. 전자는 트리거로 나는 ngOnInit보다는 ngAfterViewInit 후크의 구성 요소를 추가하는 코드를 조정 한 후자 변경 감지. ( github.com/angular/angular/issues/10131 및 유사한 스레드를 참조하십시오 .)
Rene Hamburger

1
단정하고 간단합니다. dev에서 브라우저를 통해 게재 할 때 예상대로 작동했습니다. 그러나 이것은 AOT와 함께 작동합니까? 컴파일 후 앱을 PROD에서 실행하면 구성 요소 컴파일을 시도하는 순간 "오류 : 런타임 컴파일러가로드되지 않습니다"라는 메시지가 나타납니다. (btw, Ionic 3.5를 사용하고 있습니다)
mymo

52

나는 파티에 늦게 도착했을 것입니다. 여기에서 해결책 중 어느 것도 나에게 도움이되지 않았습니다. 너무 지저분하고 너무 많은 해결책처럼 느껴졌습니다.

내가하고 결국은 사용하고 Angular 4.0.0-beta.6ngComponentOutlet을 .

이것은 동적 구성 요소 파일에 작성된 가장 짧고 간단한 솔루션을 제공했습니다.

  • 다음은 텍스트를 수신하여 템플릿에 넣는 간단한 예입니다. 그러나 필요에 따라 변경할 수 있습니다.
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;
  }
}
  • 간단한 설명 :
    1. my-component -동적 컴퍼넌트가 렌더링되는 컴퍼넌트
    2. DynamicComponent -동적으로 작성 될 컴포넌트이며 my-component 내부에서 렌더링 중입니다.

모든 각도 라이브러리를 ^ Angular 4.0.0으로 업그레이드하는 것을 잊지 마십시오

이것이 도움이되기를 바랍니다. 행운을 빕니다!

최신 정보

각도 5에서도 작동합니다.


3
이것은 Angular4에서 나에게 효과적이었습니다. 내가해야 할 유일한 조정은 동적으로 생성 된 RuntimeComponentModule에 대한 가져 오기 모듈을 지정할 수 있다는 것입니다.
Rahul Patel

8
다음은 Angular 빠른 시작에서 시작하는 간단한 예입니다. embed.plnkr.co/9L72KpobVvY14uiQjo4p
Rahul Patel

5
이 솔루션이 "ng build --prod"와 함께 작동합니까? 컴파일러 클래스와 AoT가 atm에 맞지 않는 것 같습니다.
Pierre Chavaroche

2
@OphirStern 또한 접근 방식이 Angular 5에서는 잘 작동하지만 --prod 빌드 플래그와는 작동하지 않는다는 것을 발견했습니다.
TaeKwonJoe

2
JitCompilerFactory를 사용하여 각도 5 (5.2.8)로 테스트했지만 --prod 플래그를 사용하면 작동하지 않습니다! 누구든지 해결책이 있습니까? (--prod 플래그가없는 BTW JitCompilerFactory는 완벽하게 작동합니다)
Frank

20

2019 년 6 월 답변

좋은 소식! @ angular / cdk 패키지는 이제 포털에 대한 일류 지원을 제공 하는 것 같습니다 !

글을 쓰는 시점에서 위의 공식 문서는 특히 도움이되지 않았습니다 (특히 동적 구성 요소로 데이터를 보내고 이벤트를받는 것과 관련하여). 요약하면 다음이 필요합니다.

1 단계) 업데이트 AppModule

패키지 PortalModule에서 가져 와서 @angular/cdk/portal동적 컴포넌트를 내부에 등록하십시오entryComponents

@NgModule({
  declarations: [ ..., AppComponent, MyDynamicComponent, ... ]
  imports:      [ ..., PortalModule, ... ],
  entryComponents: [ ..., MyDynamicComponent, ... ]
})
export class AppModule { }

단계 2. 옵션 A : 동적 구성 요소로 데이터를 전달하거나 이벤트를 수신 할 필요가없는 경우 :

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add child component</button>
    <ng-template [cdkPortalOutlet]="myPortal"></ng-template>
  `
})
export class AppComponent  {
  myPortal: ComponentPortal<any>;
  onClickAddChild() {
    this.myPortal = new ComponentPortal(MyDynamicComponent);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child.</p>`
})
export class MyDynamicComponent{
}

실제로보기

2 단계. 옵션 B : 동적 구성 요소로 데이터를 전달하고 동적 구성 요소로부터 이벤트를 수신해야하는 경우 :

// A bit of boilerplate here. Recommend putting this function in a utils 
// file in order to keep your component code a little cleaner.
function createDomPortalHost(elRef: ElementRef, injector: Injector) {
  return new DomPortalHost(
    elRef.nativeElement,
    injector.get(ComponentFactoryResolver),
    injector.get(ApplicationRef),
    injector
  );
}

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add random child component</button>
    <div #portalHost></div>
  `
})
export class AppComponent {

  portalHost: DomPortalHost;
  @ViewChild('portalHost') elRef: ElementRef;

  constructor(readonly injector: Injector) {
  }

  ngOnInit() {
    this.portalHost = createDomPortalHost(this.elRef, this.injector);
  }

  onClickAddChild() {
    const myPortal = new ComponentPortal(MyDynamicComponent);
    const componentRef = this.portalHost.attach(myPortal);
    setTimeout(() => componentRef.instance.myInput 
      = '> This is data passed from AppComponent <', 1000);
    // ... if we had an output called 'myOutput' in a child component, 
    // this is how we would receive events...
    // this.componentRef.instance.myOutput.subscribe(() => ...);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child. <strong>{{myInput}}</strong></p>`
})
export class MyDynamicComponent {
  @Input() myInput = '';
}

실제로보기


1
야, 방금 못 박았 어 이것은 주목을받을 것입니다. 나는 그것을해야 할 때까지 Angular에 간단한 동적 구성 요소를 추가하는 것이 얼마나 어려운지를 믿을 수 없었습니다. 재설정을 수행하고 JQuery 이전 시간으로 돌아가는 것과 같습니다.
Gi1ber7 '

2
@ Gi1ber7 알지? 왜 이렇게 오래 걸렸습니까?
Stephen Paul

1
좋은 접근법이지만 매개 변수를 ChildComponent에 전달하는 방법을 알고 있습니까?
Snook

1
@Snook이 질문에 대한 답변이 될 수도 있습니다. stackoverflow.com/questions/47469844/…
Stephen Paul

4
@StephenPaul 어떻게 않는 Portal접근 방식은 다를 ngTemplateOutletngComponentOutlet? 🤔
Glenn Mohammad

18

나는 배운 모든 것을 하나의 파일로 압축하기로 결정했다 . 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 {}`

10

각도 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 { }

(이 예제에서와 같이 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


3
따라서 module1, module2, module3을 선언했습니다. 또 다른 "동적"템플릿 컨텐츠가 필요하다면 moudle4 (module4.ts) 형식의 정의 (파일)를 작성해야합니다. 그렇다면 동적이지 않은 것 같습니다. 정적이지 않습니까? 아니면 내가 뭔가를 그리워합니까?
Radim Köhler

위의 "template1"은 html 문자열입니다. 아무 것도 넣을 수 있습니다.이 질문에
Long Field

6

각도 7.x에서는 각도 요소를 사용했습니다.

  1. @ angular-elements npm i @ angular / elements -s 설치

  2. 액세서리 서비스를 만듭니다.

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"을 사용했습니다.

  1. 그런 다음 AppComponent에서 register를 호출해야합니다.
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
  1. 이제 코드의 어느 곳에서나 다음과 같이 사용할 수 있습니다.
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를 사용하여 객체를 전달한 후 다시 구문 분석해야합니다. 더 나은 해결책을 찾을 수 없습니다.


접근 방식을 인터 휴식,하지만 tsconfig.json에서 (IE11에 대한 지원이 있도록),이에 실패합니다 othewise 없습니다 당신은 es2015을 대상으로해야합니다document.createElement(tagName);
농어

안녕하세요, 입력을 처리하는 방법을 언급했듯이 자식 구성 요소의 출력도 이와 같이 처리 할 수 ​​있습니까?
Mustahsan

5

ng-dynamicdynamicComponent 지시문을 사용하여 Angular 2 Final 버전에서 간단히 해결했습니다 .

용법:

<div *dynamicComponent="template; context: {text: text};"></div>

여기서 template은 동적 템플릿이며 컨텍스트는 템플릿을 바인딩하려는 동적 데이터 모델로 설정할 수 있습니다.


AOT와 함께 Angular 5를 작성할 때 JIT 컴파일러가 번들에 포함되어 있지 않으므로이를 지원하지 않습니다. AOT가 없으면 그것은 매력처럼 작동합니다 :)
Richard Houltz

이것은 여전히 ​​각도 7 이상에 적용됩니까?
Carlos E

4

Radim의이 우수한 게시물 위에 몇 가지 세부 정보를 추가하고 싶습니다.

나는이 솔루션을 가져 와서 약간의 노력을 기울 였고 몇 가지 한계에 빨리 부딪쳤다. 나는 그것들을 간략하게 설명하고 그에 대한 해결책을 줄 것이다.

  • 우선 동적 세부 정보 내에서 동적 세부 정보를 렌더링 할 수 없었습니다 (기본적으로 동적 UI를 서로 중첩시킵니다).
  • 다음 문제는 솔루션에서 제공되는 부품 중 하나 안에 동적 세부 정보를 렌더링하려고한다는 것입니다. 초기 솔루션으로는 불가능했습니다.
  • 마지막으로 string-editor와 같은 동적 부분에서 템플릿 URL을 사용할 수 없었습니다.

이 게시물을 기반으로 다음과 같은 한계를 달성하는 방법에 대한 또 다른 질문을했습니다.

angular2에서 재귀 동적 템플릿 컴파일

솔루션의 유연성을 높이기 위해 나와 동일한 문제가 발생하는 경우 이러한 제한 사항에 대한 답변을 간략하게 설명하겠습니다. 초기 플 런처도 업데이트시키는 것이 좋습니다.

서로 동적 중첩 세부 사항을 사용하려면 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.tsdynamic.module.ts

안에 parts.module.ts당신은에 추가해야 DynamicDetail합니다DYNAMIC_DIRECTIVES

export const DYNAMIC_DIRECTIVES = [
   forwardRef(() => StringEditor),
   forwardRef(() => TextEditor),
   DynamicDetail
];

또한 dynamic.module.tsdynamicDetail은 이제 파트의 일부이므로 제거해야합니다.

@NgModule({
   imports:      [ PartsModule ],
   exports:      [ PartsModule],
})

작동하는 수정 된 플 런커는 여기에서 찾을 수 있습니다. http://plnkr.co/edit/UYnQHF?p=preview (이 문제를 해결하지 못했습니다, 나는 메신저 :-D)

마지막으로 동적 컴포넌트에서 작성된 파트에서 templateurl을 사용할 수 없었습니다. 해결책 (또는 해결 방법. 각도 버그인지 프레임 워크의 잘못된 사용인지 확실하지 않음)은 컴파일러를 생성하는 대신 생성자에 컴파일러를 작성하는 것이 었습니다.

    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


4

Radmin의 탁월한 답변에 따라 angular-cli 버전 1.0.0-beta.22 이상을 사용하는 모든 사람에게는 약간의 조정이 필요합니다.

COMPILER_PROVIDERS더 이상 가져올 수 없습니다 (자세한 내용은 angular-cli GitHub 참조 ).

해결 방법 그래서 사용하지 않는 존재 COMPILER_PROVIDERS하고 JitCompilerproviders있지만, 사용 모두에서 섹션 JitCompilerFactory에서를 '@ 각도 / 컴파일러'대신 유형 빌더 클래스 내부의이 같은 :

private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();

보시다시피 주사 불가능하므로 DI와의 의존성이 없습니다. 이 솔루션은 angular-cli를 사용하지 않는 프로젝트에서도 작동합니다.


1
그러나이 제안에 감사드립니다. " 'DynamicHtmlModule에 대한 NgModule 메타 데이터를 찾을 수 없습니다"라는 메시지가 표시됩니다. 내 구현은 stackoverflow.com/questions/40060498/…을
Cybey

2
누구든지 AOT 샘플로 JitCompiletFactory를 사용하고 있습니까? @Cybey와 동일한 오류가 있습니다
user2771738

실제로 가능하지 않은 것 같습니다. 참조하십시오 github.com/angular/angular/issues/11780 , medium.com/@isaacplmann/...stackoverflow.com/questions/42537138/...
세바스찬

2

RC4를 RC5로 업데이트 할 수있는 방법을 찾으려고 노력하고 있으므로이 항목을 우연히 발견했으며 동적 구성 요소 생성에 대한 새로운 접근 방식이 여전히 약간의 미스터리를 가지고 있으므로 구성 요소 팩토리 리졸버에 대해서는 제안하지 않습니다.

그러나 내가 제안 할 수있는 것은이 시나리오에서 구성 요소 생성에 대한 좀 더 명확한 접근 방식입니다. 템플릿에서 스위치를 사용하여 다음과 같은 조건에 따라 문자열 편집기 또는 텍스트 편집기를 만듭니다.

<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] 표현식에서 "["는 의미를 가지므로 이는 데이터 바인딩을위한 한 가지 방법을 나타내므로 속성에 변수를 바인딩 할 필요가없는 경우에는 생략 할 수 있습니다.


1
switch/ case가 약간의 결정을 포함 한다면 갈 수있는 방법이 될 것 입니다. 그러나 생성 된 템플릿이 실제로 클 수 있으며 각 엔터티에 따라 다를 수 있으며 보안에 따라 다르며 엔터티 상태에 따라 각 속성 유형 (번호, 날짜, 참조 ... 편집기)에 따라 다를 수 있습니다. html 템플릿으로 이것을 해결하면 ngSwitch매우 큰 html파일 이 생성 됩니다.
Radim Köhler

아 동의합니다. 컴파일 할 특정 클래스를 알지 못하고 응용 프로그램의 주요 구성 요소를로드하려고 할 때 지금 여기에 이런 종류의 시나리오가 있습니다. 이 특정한 경우에는 동적 구성 요소 작성이 필요하지 않습니다.
zii

1

서버에서 생성 된 동적 양식 컨트롤의 예입니다.

https://stackblitz.com/edit/angular-t3mmg6

이 예제는 동적 양식 컨트롤이 컴포넌트 추가에 있습니다 (여기서는 서버에서 Formcontrol을 가져올 수 있습니다). addcomponent 메소드가 표시되면 Forms Controls를 볼 수 있습니다. 이 예제에서는 각도 재질을 사용하지 않지만 작동합니다 (@ work 사용). 이것은 각도 6을 대상으로하지만 이전의 모든 버전에서 작동합니다.

AngularVersion 5 이상에 JITComplierFactory를 추가해야합니다.

감사

비제이


0

이 특별한 경우에는 지시문을 사용하여 구성 요소를 동적으로 작성하는 것이 더 나은 옵션 인 것 같습니다. 예:

컴포넌트를 작성하려는 HTML에서

<ng-container dynamicComponentDirective [someConfig]="someConfig"></ng-container>

다음과 같은 방법으로 지시문에 접근하고 디자인합니다.

const components: {[type: string]: Type<YourConfig>} = {
    text : TextEditorComponent,
    numeric: NumericComponent,
    string: StringEditorComponent,
    date: DateComponent,
    ........
    .........
};

@Directive({
    selector: '[dynamicComponentDirective]'
})
export class DynamicComponentDirective implements YourConfig, OnChanges, OnInit {
    @Input() yourConfig: Define your config here //;
    component: ComponentRef<YourConfig>;

    constructor(
        private resolver: ComponentFactoryResolver,
        private container: ViewContainerRef
    ) {}

    ngOnChanges() {
        if (this.component) {
            this.component.instance.config = this.config;
            // config is your config, what evermeta data you want to pass to the component created.
        }
    }

    ngOnInit() {
        if (!components[this.config.type]) {
            const supportedTypes = Object.keys(components).join(', ');
            console.error(`Trying to use an unsupported type ${this.config.type} Supported types: ${supportedTypes}`);
        }

        const component = this.resolver.resolveComponentFactory<yourConfig>(components[this.config.type]);
        this.component = this.container.createComponent(component);
        this.component.instance.config = this.config;
    }
}

따라서 구성 요소 텍스트, 문자열, 날짜 등 ng-container요소 에서 HTML로 전달한 구성을 사용할 수 있습니다.

구성 yourConfig은 동일하고 메타 데이터를 정의 할 수 있습니다.

구성 또는 입력 유형에 따라 지시문은 그에 따라 작동하고 지원되는 유형에 따라 적절한 구성 요소를 렌더링합니다. 그렇지 않으면 오류가 기록됩니다.


-1

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;

  }

}

도움이 되었기를 바랍니다.

건배!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.