เทียบเท่ากับ $ คอมไพล์ใน Angular 2


136

ฉันต้องการรวบรวม 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 มาพร้อมกับ ComponentFactoryResolver ซึ่งเทียบเท่ากับ $ คอมไพล์ใน Angular 1.0 ดูคำตอบของฉันstackoverflow.com/questions/34784778/…
Code-EZ

1
@ danday74 - ฉันยอมรับว่าไม่มีคำตอบใดที่ให้ความสามารถในการรวบรวมเทมเพลต HTML ตามอำเภอใจ แต่เพียงแค่เลือกจากชุดส่วนประกอบที่มีอยู่แล้ว ฉันพบคำตอบที่แท้จริงที่นี่ซึ่งทำงานใน Angular 8 เป็นอย่างน้อย: stackoverflow.com/questions/61137899/… . ดูคำตอบเดียวซึ่งมี StackBlitz ที่ใช้งานได้ซึ่งรวบรวมเทมเพลต HTML ที่สร้างขึ้นตามเวลาทำงานโดยพลการ
EbenH

คำตอบ:


133

เชิงมุม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 นั่นคือการอ้างอิง lo-dash ใน index.html เพียงแค่เพิ่มการอ้างอิงนั้นและทั้งหมดจะใช้ได้
Radim Köhler

64
นี่มันยากจริงๆเหรอ? ฉันเคยสามารถทำอะไรแบบนี้ได้$compile($element.contents())($scope.$new());และตอนนี้มันเป็นโค้ดหลายร้อยบรรทัดพร้อมด้วยการสร้าง NgModule ... นี่คือสิ่งที่ทำให้ฉันต้องการหลีกเลี่ยง NG2 และก้าวไปสู่สิ่งที่ดีกว่า
Karvapallo

2
ข้อดีของการใช้JitCompilerหากตัวอย่างของคุณใช้งานได้Compilerจาก@angular/coreอะไร? plnkr.co/edit/UxgkiT?p=preview
yurzui

4
โอ้พระเจ้าฉันควรเขียนโค้ดกี่บรรทัดเพื่อรวบรวมองค์ประกอบเล็ก ๆ ฉันไม่ค่อยเข้าใจ
Mr_Perfect

35

หมายเหตุ: ดังที่ @BennyBottema กล่าวถึงในความคิดเห็นตอนนี้ DynamicComponentLoader เลิกใช้แล้วดังนั้นคำตอบนี้จึงเป็นเช่นนั้น


Angular2 ไม่มี $ compile ที่เทียบเท่า คุณสามารถใช้DynamicComoponentLoaderและสับกับการเรียน ES6 รวบรวมรหัสของคุณแบบไดนามิก (เห็นนี้ตีเสียงดังปัง ):

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
ตอบคำถามของฉันเอง: เป็นไปได้ผ่านการส่งข้อมูลไปยังฟังก์ชันคอมไพล์ ที่นี่plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko

โซลูชันนี้ใช้ได้เฉพาะกับเบต้า -0 จากเบต้า 1 ถึง 15 โค้ดตัวอย่างจะแสดงข้อผิดพลาด ข้อผิดพลาด: ไม่มีคำสั่งองค์ประกอบที่องค์ประกอบ [วัตถุวัตถุ]
Nicolas Forney

13
เนื่องจาก RC1 DynamicComponentLoader เลิกใช้งานแล้ว
Benny Bottema

1
@BennyBottema ตั้งแต่DynamicComponentLoaderเลิกใช้แล้วเราจะทำสิ่งเดียวกันใน Angular 2 ได้อย่างไร สมมติว่าฉันมีกล่องโต้ตอบโมดอลและฉันต้องการโหลดส่วนประกอบใหม่แบบไดนามิกตามเนื้อหา
ลุคทีโอไบรอัน

16

Angular Version ที่ฉันใช้ - Angular 4.2.0

Angular 4 มาพร้อมกับComponentFactoryResolverเพื่อโหลดส่วนประกอบที่รันไทม์ นี่คือการใช้$ คอมไพล์แบบเดียวกัน ใน Angular 1.0 ซึ่งตอบสนองความต้องการของคุณ

ในตัวอย่างด้านล่างนี้ฉันกำลังโหลด คอมโพเนนต์ImageWidgetแบบไดนามิกในDashboardTileComponent

ในการโหลดส่วนประกอบคุณต้องมีคำสั่งที่คุณสามารถนำไปใช้กับng-template ซึ่งจะช่วยในการวางองค์ประกอบไดนามิก

WidgetHostDirective

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

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


      }
    }

คำสั่งนี้ฉีดViewContainerRefเพื่อเข้าถึงคอนเทนเนอร์มุมมองขององค์ประกอบที่จะโฮสต์องค์ประกอบที่เพิ่มแบบไดนามิก

DashboardTileComponent (ส่วนประกอบตัวยึดตำแหน่งเพื่อแสดงผลองค์ประกอบแบบไดนามิก)

ส่วนประกอบนี้ยอมรับอินพุตที่มาจากส่วนประกอบหลักหรือคุณสามารถโหลดจากบริการของคุณตามการใช้งานของคุณ คอมโพเนนต์นี้ทำหน้าที่หลักในการแก้ไขคอมโพเนนต์ขณะรันไทม์ ในวิธีนี้คุณยังสามารถดูวิธีการที่ชื่อrenderComponent ()ซึ่งท้ายที่สุดจะโหลดชื่อส่วนประกอบจากบริการและแก้ไขด้วยComponentFactoryResolverและในที่สุดก็ตั้งค่าข้อมูลเป็นองค์ประกอบไดนามิก

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() {

    }
}

กระเบื้อง

 export interface TileModel {
      data: any;
    }

การอ้างอิงดั้งเดิมจากบล็อกของฉัน

เอกสารอย่างเป็นทางการ

ดาวน์โหลดซอร์สโค้ดตัวอย่าง


1
entryComponentsคุณลืมที่จะพูดถึงเกี่ยวกับ หากไม่มีวิธีแก้ปัญหาของคุณจะไม่ได้ผล
yurzui

ComponentFactoryResolver อยู่ใน angular2 และฉันคิดว่ามันไม่เทียบเท่ากับ $ compile
yurzui

@yurzui. แต่มันตอบสนองความต้องการของ $ compile ใช่ไหม ??
Code-EZ

@yurzui ฉันใช้การใช้งานแบบเดียวกันโดยใช้ $ compile เมื่อเราลบส่วนประกอบรายการออกจากโมดูลมันจะทำให้เกิดข้อผิดพลาด ImageTextWidgetComponent ไม่ถูกโหลด แต่แอปพลิเคชันยังใช้งานได้
Code-EZ

1
@BecarioSenior ถ้าคุณไม่ได้แคสต์ไปยังคลาสโมเดลใด ๆ มันจะเป็นไดนามิกเริ่มต้น ในตัวอย่างนี้ประเภทของข้อมูลคุณสมบัติคืออะไรก็ได้ซึ่งหมายความว่าคุณสามารถส่งข้อมูลใด ๆ ไปยังองค์ประกอบไดนามิกเป็นอินพุตได้ ช่วยให้อ่านโค้ดได้ง่ายขึ้น
Code-EZ


3

ในการสร้างอินสแตนซ์ของส่วนประกอบแบบ dinamically และแนบเข้ากับ 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 )
11mb

3

Angular TypeScript / ES6 (เชิงมุม 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

2

คุณสามารถดูส่วนประกอบที่อนุญาตให้รวบรวมส่วนประกอบเชิงมุมแบบไดนามิกอย่างง่ายhttps://www.npmjs.com/package/@codehint-ng/html-compiler


มันใช้งานได้โดยใช้ไลบรารีนี้! :) ขอบคุณสำหรับคำแนะนำ
Houssem Chlegou

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

-6

หากคุณต้องการแทรกโค้ด 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.