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

템플릿을 동적으로 만들고 싶습니다. 이것은을 구축하는 데 사용되어야 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. 의 솔루션이 필요합니다 .



답변

편집 -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 관련) 을 보려면 기록을 확인하십시오.


답변

편집 (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거주하십시오 .


답변

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

내가하고 결국은 사용하고 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에서도 작동합니다.


답변

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 = '';
}

실제로보기


답변

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


답변

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


답변

각도 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를 사용하여 객체를 전달한 후 다시 구문 분석해야합니다. 더 나은 해결책을 찾을 수 없습니다.