Tương đương với $ biên dịch trong Angular 2


134

Tôi muốn tự biên dịch một số lệnh chứa HTML. Tương đương $compiletrong Angular 2 là gì?

Ví dụ, trong Angular 1, tôi có thể tự động biên dịch một đoạn HTML và nối nó vào DOM:

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

8
Hầu hết các câu trả lời này (ngoại trừ 1 câu trả lời không dùng nữa) KHÔNG tương đương với biên dịch 1 $ góc. $ compile lấy một chuỗi HTML và biên dịch các thành phần và biểu thức có trong đó. Những câu trả lời này chỉ đơn giản là tạo các thành phần được xác định trước (chưa được khởi tạo) một cách linh hoạt và CANNOT có một đối số chuỗi. Đây không phải là điều tương tự. Có ai biết câu trả lời thực sự cho câu hỏi này?
danday74


Góc 4 đã đưa ra ComponentFactoryResolver đó tương đương với $ biên dịch trong góc 1,0 .See câu trả lời của tôi stackoverflow.com/questions/34784778/...
Code-EZ

1
@ danday74 - Tôi đồng ý rằng không có câu trả lời nào trong số này cung cấp khả năng biên dịch các mẫu HTML tùy ý, thay vào đó chúng chỉ chọn từ một tập hợp các thành phần có sẵn. Tôi đã tìm thấy câu trả lời thực sự ở đây, hoạt động trong Angular 8 ít nhất: stackoverflow.com/questions/61137899/ . Xem một câu trả lời, cung cấp một StackBlitz hoạt động để biên dịch một mẫu HTML được tạo theo thời gian chạy tùy ý.
EbenH

Câu trả lời:


132

Góc 2.3.0 (2016-12-07)

Để có được tất cả các chi tiết kiểm tra:

Để thấy rằng trong hành động:

Hiệu trưởng:

1) Tạo mẫu
2) Tạo thành phần
3) Tạo mô-đun
4) Biên dịch mô-đun
5) Tạo (và bộ đệm) Thành phần
6) sử dụng Target để tạo một Instance của nó

Tổng quan nhanh về cách tạo Thành phần

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

Cách tiêm thành phần vào 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;
}

Một đoạn mã làm thế nào để tạo một ComponentFactory (và lưu trữ nó)

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

Một đoạn mã làm thế nào để sử dụng kết quả trên

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

Mô tả đầy đủ với tất cả các chi tiết đọc ở đây , hoặc quan sát ví dụ làm việc

.

.

OBSOLLEX - Liên quan đến RC5 góc 2.0 (chỉ RC5)

để xem các giải pháp trước cho các phiên bản RC trước, xin vui lòng, tìm kiếm trong lịch sử của bài đăng này


2
Cảm ơn rất nhiều, tôi đã tìm kiếm một ví dụ hoạt động ComponentFactoryViewContainerRefđể thay thế DynamicComponentLoader hiện không dùng nữa.
Andre Loker

1
@maxou Đó là tham chiếu lo-dash trong index.html chỉ cần thêm tham chiếu đó và tất cả sẽ hoạt động
Radim Köhler

62
Đây thực sự là khó khăn? Tôi đã từng có thể làm một cái gì đó như thế này: $compile($element.contents())($scope.$new());và bây giờ là hàng trăm dòng mã, hoàn thành với việc tạo NgModule ... Đây là loại điều khiến tôi muốn tránh xa NG2 và chuyển sang một thứ tốt hơn.
Karvapallo 6/12/2016

2
Ưu điểm của việc sử dụng là gì JitCompilernếu ví dụ bạn có thể làm việc với Compilertừ @angular/core? plnkr.co/edit/UxgkiT?p=preview
yurzui

4
Ôi chúa ơi, tôi nên viết bao nhiêu dòng mã chỉ để biên dịch một phần tử nhỏ. Tôi đã không hiểu rõ
Mr_Perinf

35

Lưu ý: Như @BennyBottema đề cập trong một nhận xét, DynamicComponentLoader hiện không được chấp nhận, do đó câu trả lời này cũng vậy.


Angular2 không có bất kỳ tương đương $ biên dịch . Bạn có thể sử dụng DynamicComoponentLoadervà hack với các lớp ES6 để biên dịch mã của bạn tự động (xem này liệng ):

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

Nhưng nó sẽ chỉ hoạt động cho đến khi trình phân tích cú pháp html nằm trong lõi angular2.


Thủ thuật tuyệt vời! nhưng trong trường hợp thành phần động của tôi có một số đầu vào thì có thể liên kết dữ liệu động không?
Eugene Gluhotorenko

2
trả lời cho câu hỏi của riêng tôi: có thể thông qua việc truyền dữ liệu đến hàm biên dịch. ở đây liệng plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko

Giải pháp này chỉ hoạt động với beta-0. Từ beta 1 đến 15, mã ví dụ trả về lỗi. Lỗi: Không có chỉ thị thành phần nào tại phần tử [đối tượng]
Nicolas Forney

13
Kể từ khi
RC1 DynamicComponentLoader

1
@BennyBottema kể từ khi DynamicComponentLoaderkhông được dùng nữa, làm thế nào để chúng ta làm điều tương tự trong Angular 2? Giả sử tôi có hộp thoại theo chế độ và tôi muốn tải động một thành phần mới theo nội dung của nó
Luke T O'Brien

16

Phiên bản góc tôi đã sử dụng - Angular 4.2.0

Angular 4 được đưa ra với ElementFactoryResolver để tải các thành phần khi chạy. Đây là một dạng triển khai tương tự của $ compile trong Angular 1.0, phục vụ nhu cầu của bạn

Trong ví dụ dưới đây, tôi đang tải thành phần ImageWidget một cách linh hoạt vào DashboardTileComponent

Để tải một thành phần, bạn cần một lệnh mà bạn có thể áp dụng cho ng-template sẽ giúp đặt thành phần động

WidgethostDirective

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

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


      }
    }

lệnh này tiêm ViewContainerRef để có quyền truy cập vào vùng chứa khung nhìn của phần tử sẽ lưu trữ thành phần được thêm động.

DashboardTileComponent (Thành phần giữ chỗ để kết xuất thành phần động)

Thành phần này chấp nhận đầu vào đến từ các thành phần chính hoặc bạn có thể tải từ dịch vụ của mình dựa trên việc triển khai. Thành phần này đang đóng vai trò chính để giải quyết các thành phần khi chạy. Trong phương thức này, bạn cũng có thể thấy một phương thức có tên là renderComponent () cuối cùng tải tên thành phần từ một dịch vụ và giải quyết với ElementFactoryResolver và cuối cùng là thiết lập dữ liệu cho thành phần động.

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

Đây là một nhà máy dịch vụ để đăng ký tất cả các thành phần mà bạn muốn giải quyết một cách linh hoạt

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

ImageTextWidgetComponent (thành phần chúng tôi đang tải khi chạy)

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

Thêm Cuối cùng, thêm ImageTextWidgetComponent vào mô-đun ứng dụng của bạn dưới dạng entryComponent

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

    }
}

Ngói

 export interface TileModel {
      data: any;
    }

Tài liệu tham khảo từ blog của tôi

Tài liệu chính thức

Tải xuống mã nguồn mẫu


1
Bạn quên đề cập đến entryComponents. Không có nó, giải pháp của bạn sẽ không hoạt động
yurzui

ElementFactoryResolver là trong angular2. Và tôi nghĩ nó không tương đương với $ compile
yurzui

@yurzui. Nhưng nó phục vụ nhu cầu biên dịch $ phải không ??
Mã-EZ

@yurzui Tôi đã được sử dụng cùng loại triển khai bằng $ compile. Khi chúng tôi xóa các thành phần mục nhập khỏi mô-đun, nó sẽ báo lỗi ImageTextWidgetComponent không được tải. Nhưng ứng dụng vẫn hoạt động
Code-EZ

1
@BecarioSenior nếu bạn không được truyền vào bất kỳ lớp mô hình nào, nó sẽ là động mặc định. Trong ví dụ này, loại dữ liệu thuộc tính là bất kỳ, có nghĩa là bạn có thể chuyển bất kỳ dữ liệu nào cho thành phần động làm đầu vào. Nó giúp dễ đọc hơn cho mã của bạn.
Mã-EZ


3

Để thực sự tạo một thể hiện của một thành phần và đính kèm nó vào DOM của bạn, bạn có thể sử dụng tập lệnh sau và sẽ hoạt động trong Angular RC :

mẫu html:

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

Thành phần bộ nạp

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
Các DynamicComponentLoader là không còn nữa: '(Sau đó đã bị phản đối, đã có ComponentResolver Và bây giờ có là ComponentFactoryResolver (. Blog.rangle.io/dynamically-creating-components-with-angular-2 )
11MB

3

Kiểu chữ góc / ES6 (Góc 2+)

Hoạt động với AOT + JIT cùng một lúc.

Tôi đã tạo cách sử dụng nó ở đây: https://github.com/patrikx3/angular-compile

npm install p3x-angular-compile

Thành phần: Nên có bối cảnh và một số dữ liệu html ...

Html:

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

1
Không rõ tiêu đề 'Angular TypeScript' nghĩa là gì. Là giải pháp vô dụng cho ES5 và ES6? Sẽ rất hữu ích khi cung cấp ví dụ về việc sử dụng gói này theo chương trình, một đối tác trực tiếp với $compile(...)($scope). Không có gì trên đó ngay cả trong repo readme.
Bình Estus


0

Tôi biết vấn đề này đã cũ, nhưng tôi đã mất nhiều tuần cố gắng tìm ra cách làm cho nó hoạt động với AOT được kích hoạt. Tôi đã có thể biên dịch một đối tượng nhưng không bao giờ có thể thực thi các thành phần hiện có. Chà, cuối cùng tôi đã quyết định thay đổi chiến thuật, vì tôi không muốn biên dịch mã nhiều như thực thi một mẫu tùy chỉnh. Tôi nghĩ là thêm html mà bất cứ ai cũng có thể làm và lặp qua các nhà máy hiện có. Khi làm như vậy tôi có thể tìm kiếm phần tử / thuộc tính / vv. đặt tên và thực thi thành phần trên HTMLEuity đó. Tôi đã có thể làm cho nó hoạt động và hình dung tôi nên chia sẻ điều này để tiết kiệm cho người khác lượng thời gian khổng lồ mà tôi đã lãng phí cho nó.

@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

Nếu bạn muốn tiêm mã html, hãy sử dụng chỉ thị

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

Nếu bạn muốn tải toàn bộ thành phần ở một nơi nào đó, hãy sử dụng DynamicComponentLoader:

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


2
Tôi muốn thêm một đoạn HTML dưới dạng một chuỗi và chuyển nó đến một trình biên dịch thành phần, sau đó nối thêm thành phần đó vào DOM của tôi. Bạn có thể đưa ra một ví dụ về cách một trong những giải pháp của bạn có thể hoạt động?
pixelbits

3
sử dụng InternalHtml không biên dịch bất kỳ thành phần nào trong htmlVar
Juanín
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.