Angular 2에서 $ compile에 해당


134

지시문을 포함하는 HTML을 수동으로 컴파일하고 싶습니다. $compileAngular 2 와 동등한 기능은 무엇입니까 ?

예를 들어 Angular 1에서 HTML 조각을 동적으로 컴파일하고 DOM에 추가 할 수 있습니다.

var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);

8
대부분의 이러한 답변 (지금은 더 이상 사용되지 않는 답변 1 제외)은 각도 1 $ 컴파일과 동일하지 않습니다. $ compile은 HTML 문자열을 받아서 거기에 포함 된 컴포넌트와 표현식을 컴파일합니다. 이 답변은 단순히 미리 정의 된 구성 요소 (아직 인스턴스화되지 않은)를 동적으로 만들고 문자열 인수를 사용할 수 없습니다. 이것은 같은 것이 아닙니다. 이 질문에 대한 실제 답변을 아는 사람이 있습니까?
danday74


Angular 4는 Angular 1.0에서 $ compile에 해당하는 ComponentFactoryResolver를 생각해 냈습니다. 내 답변을보십시오 stackoverflow.com/questions/34784778/…
Code-EZ

1
@ danday74-나는이 답변 중 어느 것도 임의의 HTML 템플릿을 컴파일 할 수있는 기능을 제공하지 않는다는 것에 동의합니다. 대신 기존 구성 요소 세트에서 선택합니다. 나는 여기에서 실제 답변을 찾았는데, 적어도 Angular 8에서 작동합니다 : stackoverflow.com/questions/61137899/… . 임의의 런타임 생성 HTML 템플릿을 컴파일하는 작동하는 StackBlitz를 제공하는 답변 하나를 참조하십시오.
EbenH

답변:


132

각도 2.3.0 (2016-12-07)

모든 세부 사항을 확인하려면 다음을 수행하십시오.

그것을 실제로 보려면 :

교장 :

1) 템플릿 생성
2) 컴포넌트 생성
3) 모듈 생성
4) 모듈 컴파일
5) ComponentFactory 생성 (및 캐시)
6) Target을 사용하여 인스턴스 생성

구성 요소를 만드는 방법에 대한 간단한 개요

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

NgModule에 구성 요소를 주입하는 방법

createComponentModule (componentType: any) {
  @NgModule({
    imports: [
      PartsModule, // there are 'text-editor', 'string-editor'...
    ],
    declarations: [
      componentType
    ],
  })
  class RuntimeComponentModule
  {
  }
  // a module for just this Type
  return RuntimeComponentModule;
}

코드 스 니펫을 생성 하고 캐시 하는 방법ComponentFactory

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

위 결과를 사용하는 코드 스 니펫

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

자세한 내용 은 여기를 읽 거나 작업 예를 참조하십시오.

.

.

OBSOLETE-Angular 2.0 RC5 관련 (RC5 전용)

이전 RC 버전에 대한 이전 솔루션을 보려면 이 게시물의 내역을 검색하십시오.


2
덕분에 많이 나는의 작업 예제를 찾고 있었다 ComponentFactory그리고 ViewContainerRef지금은 사용되지 않는 DynamicComponentLoader을 대체 할 수 있습니다.
Andre Loker

1
@maxou 그것은 index.html에 lo-dash 참조입니다. 그 참조를 추가하면 모든 것이 작동합니다
Radim Köhler

62
이것이 정말로 어려운가요? 예전에는 다음과 같은 작업을 수행 할 수있었습니다. $compile($element.contents())($scope.$new());이제 NgModule 생성이 완료된 수백 줄의 코드가 있습니다. 이것은 NG2를 피하고 더 나은 것으로 나아가고 자하는 종류의 것입니다.
Karvapallo

2
JitCompiler예제가 Compilerfrom에서 작동 하는 경우 사용 하면 어떤 이점이 @angular/core있습니까? plnkr.co/edit/UxgkiT?p= 미리보기
yurzui

4
오 마이 갓, 작은 요소를 컴파일하기 위해 몇 줄의 코드를 작성해야합니까? 잘
몰랐어요

35

참고 : @BennyBottema가 주석에서 언급했듯이 DynamicComponentLoader는 더 이상 사용되지 않으므로이 대답도 마찬가지입니다.


Angular2에는 $ compile에 해당하는 것이 없습니다 . DynamicComoponentLoaderES6 클래스를 사용 하고 해킹하여 코드를 동적으로 컴파일 할 수 있습니다 (이 plunk 참조 ).

import {Component, DynamicComponentLoader, ElementRef, OnInit} from 'angular2/core'

function compileToComponent(template, directives) {
  @Component({ 
    selector: 'fake', 
    template , directives
  })
  class FakeComponent {};
  return FakeComponent;
}

@Component({
  selector: 'hello',
  template: '<h1>Hello, Angular!</h1>'
})
class Hello {}

@Component({
  selector: 'my-app',
  template: '<div #container></div>',
})
export class App implements OnInit {
  constructor(
    private loader: DynamicComponentLoader, 
    private elementRef: ElementRef,
  ) {}

  ngOnInit() {} {
    const someDynamicHtml = `<hello></hello><h2>${Date.now()}</h2>`;

    this.loader.loadIntoLocation(
      compileToComponent(someDynamicHtml, [Hello])
      this.elementRef,
      'container'
    );
  }
}

그러나 html 파서가 angular2 코어 안에있을 때까지만 작동합니다.


멋진 트릭! 그러나 내 동적 구성 요소에 입력이있는 경우 동적 데이터도 바인딩 할 수 있습니까?
Eugene Gluhotorenko

2
내 자신의 질문에 대답 : 데이터를 컴파일 함수에 전달하여 가능합니다. 여기 pplkrkr.co/edit/dK6l7jiWt535jOw1Htct?p= 미리보기
Eugene Gluhotorenko 19.21의

이 솔루션은 베타 -0에서만 작동합니다. 베타 1에서 15까지 예제 코드는 오류를 반환합니다. 오류 : [object Object] 요소에 구성 요소 지시문이 없습니다.
Nicolas Forney

13
rc1 DynamicComponentLoader가 폐기 된 이후
Benny Bottema

1
@BennyBottema DynamicComponentLoader는 더 이상 사용되지 않으므로 Angular 2에서 동일한 종류의 작업을 어떻게 수행합니까? 모달 대화 상자가 있고 내용에 따라 새 구성 요소를 동적으로로드한다고 가정합니다.
Luke T O'Brien

16

내가 사용한 Angular 버전 -Angular 4.2.0

Angular 4는 런타임에 구성 요소 를로드하기 위해 ComponentFactoryResolver 와 함께 제공 됩니다. 이것은 Angular 1.0에서 $ compile 을 구현 한 것입니다.

이 아래 예제에서는 ImageWidget 구성 요소를 DashboardTileComponent에 동적으로 로드 합니다.

구성 요소를로드 하려면 동적 구성 요소를 배치하는 데 도움이되는 ng-template에 적용 할 수있는 지시문이 필요합니다.

WidgetHostDirective

 import { Directive, ViewContainerRef } from '@angular/core';

    @Directive({
      selector: '[widget-host]',
    })
    export class DashboardTileWidgetHostDirective {
      constructor(public viewContainerRef: ViewContainerRef) { 


      }
    }

이 지시문은 ViewContainerRef 를 주입 하여 동적으로 추가 된 구성 요소를 호스팅 할 요소의 뷰 컨테이너에 액세스합니다.

DashboardTileComponent (동적 구성 요소를 렌더링하기위한 자리 표시 자 구성 요소)

이 구성 요소는 상위 구성 요소에서 오는 입력을 승인하거나 구현에 따라 서비스에서로드 할 수 있습니다. 이 구성 요소는 런타임시 구성 요소를 해결하기 위해 주요 역할을 수행합니다. 이 메소드에서는 궁극적으로 서비스에서 컴포넌트 이름을로드하고 ComponentFactoryResolver로 분석 하고 마지막으로 데이터를 동적 컴포넌트로 설정 하는 renderComponent () 메소드를 볼 수 있습니다 .

import { Component, Input, OnInit, AfterViewInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core';
import { DashboardTileWidgetHostDirective } from './DashbardWidgetHost.Directive';
import { TileModel } from './Tile.Model';
import { WidgetComponentService } from "./WidgetComponent.Service";


@Component({
    selector: 'dashboard-tile',
    templateUrl: 'app/tile/DashboardTile.Template.html'
})

export class DashboardTileComponent implements OnInit {
    @Input() tile: any;
    @ViewChild(DashboardTileWidgetHostDirective) widgetHost: DashboardTileWidgetHostDirective;
    constructor(private _componentFactoryResolver: ComponentFactoryResolver,private widgetComponentService:WidgetComponentService) {

    }

    ngOnInit() {

    }
    ngAfterViewInit() {
        this.renderComponents();
    }
    renderComponents() {
        let component=this.widgetComponentService.getComponent(this.tile.componentName);
        let componentFactory = this._componentFactoryResolver.resolveComponentFactory(component);
        let viewContainerRef = this.widgetHost.viewContainerRef;
        let componentRef = viewContainerRef.createComponent(componentFactory);
        (<TileModel>componentRef.instance).data = this.tile;

    }
}

DashboardTileComponent.html

 <div class="col-md-2 col-lg-2 col-sm-2 col-default-margin col-default">        
                        <ng-template widget-host></ng-template>

          </div>

WidgetComponentService

동적으로 해결하려는 모든 구성 요소를 등록하는 서비스 팩토리입니다.

import { Injectable }           from '@angular/core';
import { ImageTextWidgetComponent } from "../templates/ImageTextWidget.Component";
@Injectable()
export class WidgetComponentService {
  getComponent(componentName:string) {
          if(componentName==="ImageTextWidgetComponent"){
              return ImageTextWidgetComponent
          }
  }
}

ImageTextWidgetComponent (런타임에로드하는 구성 요소)

import { Component, OnInit, Input } from '@angular/core';


@Component({
    selector: 'dashboard-imagetextwidget',
    templateUrl: 'app/templates/ImageTextWidget.html'
})

export class ImageTextWidgetComponent implements OnInit {
     @Input() data: any;
    constructor() { }

    ngOnInit() { }
}

마지막으로이 ImageTextWidgetComponent를 entryComponent로 앱 모듈에 추가하십시오.

@NgModule({
    imports: [BrowserModule],
    providers: [WidgetComponentService],
    declarations: [
        MainApplicationComponent,
        DashboardHostComponent,
        DashboardGroupComponent,
        DashboardTileComponent,
        DashboardTileWidgetHostDirective,
        ImageTextWidgetComponent
        ],
    exports: [],
    entryComponents: [ImageTextWidgetComponent],
    bootstrap: [MainApplicationComponent]
})
export class DashboardModule {
    constructor() {

    }
}

TileModel

 export interface TileModel {
      data: any;
    }

내 블로그의 원래 참조

공식 문서

샘플 소스 코드 다운로드


1
에 대해 언급하는 것을 잊었습니다 entryComponents. 그것 없이는 당신의 해결책이 작동하지 않을 것입니다
yurzui

ComponentFactoryResolver가 angular2에있었습니다. 그리고 나는 그것이 $ compile과 같지 않다고 생각합니다
yurzui

@yurzui. 그러나 그것은 바로 $ compile의 필요성을 충족 시키는가?
Code-EZ

@ yurzui $ compile을 사용하여 동일한 종류의 구현을 사용했습니다. 모듈에서 입력 컴포넌트를 제거하면 ImageTextWidgetComponent가로드되지 않았다는 오류가 발생합니다. 그러나 응용 프로그램은 여전히 ​​작동합니다
Code-EZ

1
@BecarioSenior 모델 클래스에 캐스트되지 않으면 기본 동적입니다. 이 예제에서 특성 데이터 유형은 any이므로 모든 데이터를 동적 컴포넌트에 입력으로 전달할 수 있습니다. 코드를 더 쉽게 읽을 수 있습니다.
Code-EZ


3

컴포넌트의 인스턴스를 동적으로 생성하여 DOM에 연결하려면 다음 스크립트를 사용할 수 있으며 Angular RC 에서 작동해야합니다 .

html 템플릿 :

<div>
  <div id="container"></div>
  <button (click)="viewMeteo()">Meteo</button>
  <button (click)="viewStats()">Stats</button>
</div>

로더 구성 요소

import { Component, DynamicComponentLoader, ElementRef, Injector } from '@angular/core';
import { WidgetMeteoComponent } from './widget-meteo';
import { WidgetStatComponent } from './widget-stat';

@Component({
  moduleId: module.id,
  selector: 'widget-loader',
  templateUrl: 'widget-loader.html',
})
export class WidgetLoaderComponent  {

  constructor( elementRef: ElementRef,
               public dcl:DynamicComponentLoader,
               public injector: Injector) { }

  viewMeteo() {
    this.dcl.loadAsRoot(WidgetMeteoComponent, '#container', this.injector);
  }

  viewStats() {
    this.dcl.loadAsRoot(WidgetStatComponent, '#container', this.injector);
  }

}

1
DynamicComponentLoader은 더 이상 없다 '(즉이되지 후, ComponentResolver이 있었다 그리고 지금 ComponentFactoryResolver (이있다. blog.rangle.io/dynamically-creating-components-with-angular-2 )
11메가바이트

3

앵귤러 타입 스크립트 / ES6 (Angular 2+)

한 번에 AOT + JIT와 함께 작동합니다.

https://github.com/patrikx3/angular-compile 여기에서 사용 방법을 만들었습니다.

npm install p3x-angular-compile

구성 요소 : 컨텍스트와 일부 HTML 데이터가 있어야합니다 ...

HTML :

<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>

1
'Angular TypeScript'제목의 의미가 확실하지 않습니다. ES5 및 ES6에 솔루션이 쓸모가 없습니까? 이 패키지의 프로그래밍 방식 사용 예를 제공하는 것이 도움이 될 것 $compile(...)($scope)입니다. repo readme에도 아무것도 없습니다.
Estus Flask


0

나는이 문제가 오래되었다는 것을 알고 있지만 AOT를 사용 하여이 작업을 수행하는 방법을 알아 내려고 몇 주를 보냈습니다. 객체를 컴파일 할 수 있었지만 기존 구성 요소를 실행할 수 없었습니다. 글쎄, 마침내 사용자 정의 템플릿을 실행하는 것만 큼 코드를 컴파일하지 않기 때문에 택트를 변경하기로 결정했습니다. 내 생각은 누구나 기존 공장을 통해 누구나 할 수있는 html을 추가하는 것이 었습니다. 그렇게하면서 요소 / 속성 / 등을 검색 할 수 있습니다. 해당 HTMLElement에서 구성 요소의 이름을 지정하고 실행합니다. 나는 그것을 작동시킬 수 있었고 내가 낭비하는 엄청난 시간을 다른 사람을 구하기 위해 이것을 공유해야한다고 생각했다.

@Component({
    selector: "compile",
    template: "",
    inputs: ["html"]
})
export class CompileHtmlComponent implements OnDestroy {
    constructor(
        private content: ViewContainerRef,
        private injector: Injector,
        private ngModRef: NgModuleRef<any>
    ) { }

    ngOnDestroy() {
        this.DestroyComponents();
    }

    private _ComponentRefCollection: any[] = null;
    private _Html: string;

    get Html(): string {
        return this._Html;
    }
    @Input("html") set Html(val: string) {
        // recompile when the html value is set
        this._Html = (val || "") + "";
        this.TemplateHTMLCompile(this._Html);
    }

    private DestroyComponents() { // we need to remove the components we compiled
        if (this._ComponentRefCollection) {
            this._ComponentRefCollection.forEach((c) => {
                c.destroy();
            });
        }
        this._ComponentRefCollection = new Array();
    }

    private TemplateHTMLCompile(html) {
        this.DestroyComponents();
        this.content.element.nativeElement.innerHTML = html;
        var ref = this.content.element.nativeElement;
        var factories = (this.ngModRef.componentFactoryResolver as any)._factories;
        // here we loop though the factories, find the element based on the selector
        factories.forEach((comp: ComponentFactory<unknown>) => {
            var list = ref.querySelectorAll(comp.selector);
            list.forEach((item) => {
                var parent = item.parentNode;
                var next = item.nextSibling;
                var ngContentNodes: any[][] = new Array(); // this is for the viewchild/viewchildren of this object

                comp.ngContentSelectors.forEach((sel) => {
                    var ngContentList: any[] = new Array();

                    if (sel == "*") // all children;
                    {
                        item.childNodes.forEach((c) => {
                            ngContentList.push(c);
                        });
                    }
                    else {
                        var selList = item.querySelectorAll(sel);

                        selList.forEach((l) => {
                            ngContentList.push(l);
                        });
                    }

                    ngContentNodes.push(ngContentList);
                });
                // here is where we compile the factory based on the node we have
                let component = comp.create(this.injector, ngContentNodes, item, this.ngModRef);

                this._ComponentRefCollection.push(component); // save for our destroy call
                // we need to move the newly compiled element, as it was appended to this components html
                if (next) parent.insertBefore(component.location.nativeElement, next);
                else parent.appendChild(component.location.nativeElement);

                component.hostView.detectChanges(); // tell the component to detectchanges
            });
        });
    }
}

-5

HTML 코드를 사용하려면 지시문을 사용하십시오.

<div [innerHtml]="htmlVar"></div>

전체 구성 요소를 특정 위치에로드하려면 DynamicComponentLoader를 사용하십시오.

https://angular.io/docs/ts/latest/api/core/DynamicComponentLoader-class.html


2
HTML 조각을 문자열로 주입하여 구성 요소 컴파일러에 전달한 다음 해당 구성 요소를 DOM에 추가하고 싶습니다. 솔루션 중 하나가 어떻게 작동하는지 예를 들어 줄 수 있습니까?
pixelbits

3
innerHtml을 사용하면 htmlVar 내부의 구성 요소가 컴파일되지 않습니다.
Juanín
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.