Làm cách nào tôi có thể sử dụng / tạo mẫu động để biên dịch Thành phần động với Angular 2.0?


197

Tôi muốn tự động tạo một mẫu. Điều này nên được sử dụng để xây dựng một ComponentTypethời gian chạy và đặt (thậm chí thay thế) nó ở đâu đó bên trong Thành phần lưu trữ.

Cho đến khi RC4 tôi đang sử dụng ComponentResolver, nhưng với RC5 tôi nhận được thông báo sau:

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.

Tôi tìm thấy tài liệu này ( Tạo thành phần động đồng bộ góc 2 )

Và hiểu rằng tôi có thể sử dụng một trong hai

  • Kiểu năng động ngIfvới ComponentFactoryResolver. Nếu tôi vượt qua các thành phần đã biết bên trong @Component({entryComponents: [comp1, comp2], ...})- tôi có thể sử dụng.resolveComponentFactory(componentToRender);
  • Biên dịch thời gian thực, với Compiler...

Nhưng câu hỏi là làm thế nào để sử dụng Compiler? Các lưu ý ở trên nói rằng tôi nên gọi: Compiler.compileComponentSync/Async- vậy làm thế nào?

Ví dụ. Tôi muốn tạo (dựa trên một số điều kiện cấu hình) loại mẫu này cho một loại cài đặt

<form>
   <string-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></string-editor>
   <string-editor
     [propertyName]="'description'"
     [entity]="entity"
   ></string-editor>
   ...

và trong trường hợp khác, cái này ( string-editorđược thay thế bằng text-editor)

<form>
   <text-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></text-editor>
   ...

Và cứ thế (số / ngày / tham chiếu editorskhác nhau theo loại thuộc tính, đã bỏ qua một số thuộc tính cho một số người dùng ...) . tức là đây là một ví dụ, cấu hình thực có thể tạo ra các mẫu phức tạp và khác nhau hơn nhiều.

Mẫu đang thay đổi, vì vậy tôi không thể sử dụng ComponentFactoryResolvervà vượt qua các mẫu hiện có ... Tôi cần một giải pháp với Compiler.


SInce giải pháp tôi tìm thấy rất hay, tôi muốn mọi người tìm thấy câu hỏi này để xem câu trả lời của tôi ở rất xa ở phía dưới cùng lúc này. :)
Richard Houltz

Bài viết Dưới đây là những gì bạn cần biết về các thành phần động trong Angular có lời giải thích tuyệt vời về các thành phần động.
Max Koretskyi

Đây là vấn đề với mọi câu trả lời ngoài kia và những gì $compilethực sự có thể làm mà các phương thức này không thể - tôi đang tạo một ứng dụng mà tôi chỉ muốn biên dịch HTML khi nó xuất hiện thông qua trang của bên thứ 3 và các cuộc gọi ajax. Tôi không thể xóa HTML khỏi trang và đặt nó vào mẫu của riêng tôi. Thở dài
Augie Gardner

@AugieGardner Có một lý do tại sao điều này là không thể bởi thiết kế. Angular không có lỗi đối với các quyết định kiến ​​trúc xấu hoặc hệ thống di sản mà một số người có. Nếu bạn muốn phân tích mã HTML hiện tại, bạn có thể sử dụng một khung công tác khác vì Angular hoạt động hoàn toàn tốt với WebComponents. Thiết lập các ranh giới rõ ràng để hướng dẫn các nhóm lập trình viên thiếu kinh nghiệm quan trọng hơn là cho phép các bản hack bẩn cho một số hệ thống cũ.
Phil

Câu trả lời:


163

EDIT - liên quan đến 2.3.0 (2016-12-07)

LƯU Ý: để có giải pháp cho phiên bản trước, hãy kiểm tra lịch sử của bài đăng này

Chủ đề tương tự được thảo luận ở đây Tương đương với $ compile trong Angular 2 . Chúng ta cần sử dụng JitCompilerNgModule. Tìm hiểu thêm về NgModuleAngular2 tại đây:

Tóm lại

một plunker / ví dụ làm việc (mẫu động, loại thành phần động, mô đun động,JitCompiler , ... đang hoạt động)

Hiệu trưởng là:
1) tạo Mẫu
2) tìm ComponentFactorytrong bộ đệm - chuyển đến 7)
3) - tạo Component
4) - tạo Module
5) - biên dịch Module
6) - trả lại (và bộ đệm để sử dụng sau) ComponentFactory
7) sử dụng TargetComponentFactory để tạo Instance năng độngComponent

Đây là một đoạn mã (nhiều hơn ở đây ) - Trình tạo tùy chỉnh của chúng tôi vừa trở lại được xây dựng / lưu vào bộ đệm ComponentFactoryvà chế độ xem Giữ chỗ mục tiêu tiêu thụ để tạo một phiên bản củaDynamicComponent

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

Đây là nó - tóm lại là vậy. Để biết thêm chi tiết .. đọc bên dưới

.

TL & DR

Quan sát một plunker và quay lại để đọc chi tiết trong trường hợp một số đoạn trích yêu cầu giải thích thêm

.

Giải thích chi tiết - Angular2 RC6 ++ và các thành phần thời gian chạy

Dưới đây mô tả kịch bản này , chúng tôi sẽ

  1. tạo một mô-đun PartsModule:NgModule (người giữ các mảnh nhỏ)
  2. tạo một mô-đun khác DynamicModule:NgModule, sẽ chứa thành phần động của chúng tôi (và tham chiếu PartsModuleđộng)
  3. tạo Mẫu động (cách tiếp cận đơn giản)
  4. tạo Componentkiểu mới (chỉ khi mẫu đã thay đổi)
  5. tạo mới RuntimeModule:NgModule. Mô-đun này sẽ chứa Componentloại được tạo trước đó
  6. gọi JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)để nhậnComponentFactory
  7. tạo một thực thể DynamicComponent- công việc của trình giữ chỗ View Target vàComponentFactory
  8. gán @Inputscho trường hợp mới (chuyển đổi từ INPUTđể TEXTAREAchỉnh sửa) , tiêu thụ@Outputs

NgModule

Chúng tôi cần một NgModules.

Mặc dù tôi muốn đưa ra một ví dụ rất đơn giản, nhưng trong trường hợp này, tôi sẽ cần ba mô-đun (thực tế là 4 - nhưng tôi không tính AppModule) . Xin vui lòng, lấy điều này thay vì một đoạn đơn giản làm cơ sở cho một trình tạo thành phần động thực sự vững chắc.

Sẽ có một mô-đun cho tất cả các thành phần nhỏ, ví dụ như string-editor, text-editor ( date-editor, number-editor...)

@NgModule({
  imports:      [ 
      CommonModule,
      FormsModule
  ],
  declarations: [
      DYNAMIC_DIRECTIVES
  ],
  exports: [
      DYNAMIC_DIRECTIVES,
      CommonModule,
      FormsModule
  ]
})
export class PartsModule { }

Nơi DYNAMIC_DIRECTIVEScó thể mở rộng và được dự định để giữ tất cả các phần nhỏ được sử dụng cho mẫu / loại Thành phần động của chúng tôi. Kiểm tra ứng dụng / bộ phận / bộ phận.module.ts

Thứ hai sẽ là mô-đun để xử lý công cụ năng động của chúng tôi. Nó sẽ chứa các thành phần lưu trữ và một số nhà cung cấp .. sẽ là singletons. Do đó, chúng tôi sẽ xuất bản chúng theo cách tiêu chuẩn - vớiforRoot()

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

Kiểm tra việc sử dụng forRoot()trongAppModule

Cuối cùng, chúng ta sẽ cần một mô-đun adhoc, thời gian chạy .. nhưng điều đó sẽ được tạo sau này, như là một phần của DynamicTypeBuildercông việc.

Mô-đun thứ tư, mô-đun ứng dụng, là người giữ các nhà cung cấp trình biên dịch khai báo:

...
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
  ],

Đọc (đọc) nhiều hơn về NgModule ở đó:

Một mẫu builder

Trong ví dụ của chúng tôi, chúng tôi sẽ xử lý chi tiết về loại thực thể này

entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
};

Để tạo một template, trong plunker này, chúng tôi sử dụng trình xây dựng đơn giản / ngây thơ này.

Giải pháp thực sự, một trình xây dựng mẫu thực sự, là nơi ứng dụng của bạn có thể làm được rất nhiều

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

Một mẹo ở đây là - nó xây dựng một mẫu sử dụng một số thuộc tính đã biết, vd entity. Thuộc tính (-ies) đó phải là một phần của thành phần động, mà chúng ta sẽ tạo tiếp theo.

Để làm cho nó dễ dàng hơn một chút, chúng ta có thể sử dụng một giao diện để xác định các thuộc tính mà trình tạo Mẫu của chúng ta có thể sử dụng. Điều này sẽ được thực hiện bởi loại Thành phần động của chúng tôi.

export interface IHaveDynamicData { 
    public entity: any;
    ...
}

Một ComponentFactoryngười xây dựng

Điều rất quan trọng ở đây là ghi nhớ:

loại thành phần của chúng tôi, xây dựng với chúng tôi DynamicTypeBuilder, có thể khác nhau - nhưng chỉ bởi mẫu của nó (được tạo ở trên) . Các thuộc tính của thành phần (đầu vào, đầu ra hoặc một số được bảo vệ) vẫn giống nhau. Nếu chúng ta cần các thuộc tính khác nhau, chúng ta nên xác định kết hợp khác nhau của Trình tạo mẫu và Kiểu

Vì vậy, chúng tôi đang chạm vào cốt lõi của giải pháp của chúng tôi. Builder, sẽ 1) tạo ComponentType2) tạo bộ đệmNgModule 3) biên dịch ComponentFactory4) để sử dụng lại sau này.

Một phụ thuộc chúng ta cần nhận:

// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
    
@Injectable()
export class DynamicTypeBuilder {

  // wee need Dynamic component builder
  constructor(
    protected compiler: JitCompiler
  ) {}

Và đây là đoạn trích làm thế nào để có được 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);
            });
    });
}

Ở trên chúng tôi tạo và lưu trữ cả hai ComponentModule. Bởi vì nếu mẫu (thực tế là phần động thực sự của tất cả) giống nhau .. chúng ta có thể sử dụng lại

Và đây là hai phương thức, đại diện cho cách thực sự tuyệt vời để tạo một lớp / kiểu trang trí trong thời gian chạy. Không chỉ @Componentmà còn@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;
}

Quan trọng:

các loại động thành phần của chúng tôi khác nhau, nhưng chỉ bằng mẫu. Vì vậy, chúng tôi sử dụng thực tế đó để lưu trữ chúng. Điều này thực sự rất quan trọng. Angular2 cũng sẽ lưu trữ những cái này .. theo loại . Và nếu chúng ta tạo lại cho cùng một chuỗi các kiểu mẫu mới ... chúng ta sẽ bắt đầu tạo ra rò rỉ bộ nhớ.

ComponentFactory được sử dụng bởi thành phần lưu trữ

Phần cuối cùng là một thành phần, lưu trữ mục tiêu cho thành phần động của chúng ta, ví dụ <div #dynamicContentPlaceHolder></div>. Chúng tôi có một tham chiếu đến nó và sử dụng ComponentFactoryđể tạo ra một thành phần. Đó là một cách ngắn gọn, và đây là tất cả các phần của thành phần đó (nếu cần, mở plunker ở đây )

Trước tiên hãy tóm tắt báo cáo nhập khẩu:

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

Chúng tôi chỉ nhận, xây dựng mẫu và thành phần. Tiếp theo là các thuộc tính cần thiết cho ví dụ của chúng tôi (thêm ý kiến)

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

Trong kịch bản đơn giản này, thành phần lưu trữ của chúng tôi không có bất kỳ @Input. Vì vậy, nó không phải phản ứng với những thay đổi. Nhưng bất chấp thực tế đó (và để sẵn sàng cho những thay đổi sắp tới) - chúng tôi cần giới thiệu một số cờ nếu thành phần đã có (trước tiên) . Và chỉ sau đó chúng ta có thể bắt đầu phép thuật.

Cuối cùng, chúng tôi sẽ sử dụng trình xây dựng thành phần của chúng tôi và nó chỉ được biên dịch / lưu trữ ComponentFacotry . Chúng tôi giữ chỗ Target sẽ được yêu cầu phải nhanh chóng cácComponent với nhà máy đó.

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

mở rộng nhỏ

Ngoài ra, chúng ta cần giữ một tham chiếu đến mẫu đã biên dịch .. để có thể thực hiện đúng destroy(), bất cứ khi nào chúng ta sẽ thay đổi nó.

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

làm xong

Đó là khá nhiều đó. Đừng quên phá hủy bất cứ thứ gì được xây dựng một cách linh hoạt (ngOnDestroy) . Ngoài ra, hãy chắc chắn để lưu trữ động typesmodulesnếu sự khác biệt duy nhất là mẫu của họ.

Kiểm tra tất cả trong hành động ở đây

để xem các phiên bản trước (ví dụ RC5 liên quan) của bài đăng này, hãy kiểm tra lịch sử


50
Trông giống như một giải pháp phức tạp như vậy, một giải pháp rất đơn giản và rõ ràng, có cách nào khác để làm điều này không?
tibbus

3
Tôi nghĩ giống như @tibbus: điều này phức tạp hơn trước đây với mã không dùng nữa. Cảm ơn câu trả lời của bạn, mặc dù.
Lucio Mollinedo

5
@ribsies cảm ơn bạn đã lưu ý. Hãy để tôi làm rõ một cái gì đó. Nhiều câu trả lời khác cố gắng làm cho nó đơn giản . Nhưng tôi đang cố gắng giải thích nó và hiển thị nó trong một kịch bản, đóng cửa để sử dụng thực sự . Chúng tôi sẽ cần phải lưu trữ công cụ, chúng tôi sẽ phải hủy hủy khi tạo lại, v.v. Vì vậy, trong khi phép thuật của tòa nhà năng động thực sự type.builder.tsnhư bạn đã chỉ, tôi ước, rằng bất kỳ người dùng nào cũng sẽ hiểu cách đặt tất cả vào đó bối cảnh ... Hy vọng nó có thể hữu ích;)
Radim Köhler

7
@Radim Köhler - Tôi đã thử ví dụ này. nó hoạt động mà không có AOT. Nhưng khi tôi thử chạy cái này với AOT thì nó báo lỗi "Không tìm thấy siêu dữ liệu NgModule cho RuntimeComponentModule". bạn có thể giúp tôi giải quyết lỗi này
Trusha

4
Câu trả lời là hoàn hảo! Nhưng đối với các ứng dụng thực tế không thể thực hiện được. Nhóm góc cạnh nên cung cấp một giải pháp cho điều này trong khuôn khổ, vì đây là yêu cầu phổ biến trong các ứng dụng kinh doanh. Nếu không, phải hỏi liệu Angular 2 có phải là nền tảng phù hợp cho các ứng dụng kinh doanh hay không.
Karl

58

EDIT (26/08/2017) : Giải pháp bên dưới hoạt động tốt với Angular2 và 4. Tôi đã cập nhật nó để chứa biến mẫu và nhấp vào trình xử lý và thử nghiệm nó với Angular 4.3.
Đối với Angular4, ngComponentOutlet như được mô tả trong câu trả lời của Ophir là một giải pháp tốt hơn nhiều. Nhưng hiện tại nó chưa hỗ trợ đầu vào & đầu ra . Nếu [PR này] ( https://github.com/angular/angular/pull/15362] được chấp nhận, có thể thông qua phiên bản thành phần được trả về bởi sự kiện tạo.
Ng-động-thành phần có thể là tốt nhất và đơn giản nhất giải pháp hoàn toàn, nhưng tôi chưa thử nghiệm điều đó.

Câu trả lời của @Long Field là tại chỗ! Đây là một ví dụ khác (đồng bộ):

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

Sống tại http://plnkr.co/edit/fdP9Oc .


3
Tôi muốn nói, đó là một ví dụ về cách viết càng ít mã càng tốt để làm giống như trong câu trả lời của tôi stackoverflow.com/a/38888009/1679310 . Trong trường hợp, đó phải là trường hợp hữu ích (chủ yếu là mẫu tạo RE) khi điều kiện thay đổi ... ngAfterViewInitcuộc gọi đơn giản với một cuộc gọi const templatesẽ không hoạt động. Nhưng nếu nhiệm vụ của bạn là giảm cách tiếp cận được mô tả chi tiết ở trên (tạo mẫu, tạo thành phần, tạo mô-đun, biên dịch nó, tạo nhà máy .. tạo ví dụ) ... thì có lẽ bạn đã làm điều đó
Radim Köhler

Cảm ơn giải pháp: Tôi gặp sự cố khi tải templateUrl và kiểu mặc dù, tôi gặp lỗi sau: Không có triển khai ResourceLoader nào được cung cấp. Không thể đọc url localhost: 3000 / app / page / Pages_common.css , bạn có biết tôi đang thiếu gì không?
Gerardlamo

Có thể biên dịch mẫu html với dữ liệu cụ thể cho ô trong lưới như điều khiển không? plnkr.co/edit/vJHUCnsJB7cwNJr2cCwp?p=preview Trong plunker này, làm cách nào tôi có thể biên dịch và hiển thị hình ảnh trong cột cuối cùng.? Có ai giúp đỡ không?
Karthick

1
@monnef, bạn nói đúng. Tôi đã không kiểm tra nhật ký giao diện điều khiển. Tôi đã điều chỉnh mã để thêm thành phần trong ngOnInit thay vì ngAfterViewInit, vì cái trước được kích hoạt trước và cái sau sau khi phát hiện thay đổi. (Xem github.com/angular/angular/issues/10131 và các chủ đề tương tự.)
Rene Hamburger

1
gọn gàng và đơn giản. Làm việc như mong đợi khi phục vụ trên trình duyệt trong dev. Nhưng điều này có hoạt động với AOT không? Khi ứng dụng được chạy trong SẢN PHẨM sau khi biên dịch, tôi nhận được "Lỗi: Trình biên dịch thời gian chạy không được tải" tại thời điểm biên dịch thành phần được thử. (btw, tôi đang sử dụng Ionic 3.5)
mymo

52

Tôi phải đến bữa tiệc muộn, không có giải pháp nào ở đây có vẻ hữu ích với tôi - quá lộn xộn và cảm thấy như quá nhiều cách giải quyết.

Những gì tôi đã làm là sử dụng Angular 4.0.0-beta.6 's ngComponentOutlet .

Điều này đã cho tôi giải pháp ngắn nhất, đơn giản nhất được ghi trong tệp của thành phần động.

  • Đây là một ví dụ đơn giản chỉ nhận văn bản và đặt nó trong một mẫu, nhưng rõ ràng bạn có thể thay đổi theo nhu cầu của mình:
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;
  }
}
  • Giải thích ngắn gọn:
    1. my-component - thành phần trong đó một thành phần động được kết xuất
    2. DynamicComponent - thành phần được xây dựng động và nó được hiển thị bên trong thành phần của tôi

Đừng quên nâng cấp tất cả các thư viện góc lên ^ Angular 4.0.0

Hy vọng điều này sẽ giúp, chúc may mắn!

CẬP NHẬT

Cũng hoạt động cho góc 5.


3
Điều này làm việc rất tốt cho tôi với Angular4. Điều chỉnh duy nhất tôi phải thực hiện là có thể chỉ định các mô-đun nhập cho RuntimeComponentModule được tạo động.
Rahul Patel

8
Dưới đây là một ví dụ nhanh bắt đầu từ Khởi động
Rahul Patel

5
Giải pháp này có hoạt động với "ng build --prod" không? Có vẻ như lớp trình biên dịch và AoT không khớp với máy atm.
Pierre Chavaroche

2
@OphirStern Tôi cũng phát hiện ra rằng cách tiếp cận hoạt động tốt trong Angular 5 nhưng KHÔNG với cờ xây dựng --prod.
TaeKwonJoe

2
Tôi đã thử nghiệm nó với góc 5 (5.2.8) bằng cách sử dụng JitCompilerFactory và sử dụng cờ --prod không hoạt động! Có ai có giải pháp không? (BTW JitCompilerFactory không có cờ --prod hoạt động hoàn hảo)
Frank

20

Câu trả lời tháng 6 năm 2019

Tin tốt! Có vẻ như gói @ angular / cdk hiện có hỗ trợ hạng nhất cho các cổng !

Vào thời điểm viết bài, tôi không thấy các tài liệu chính thức ở trên đặc biệt hữu ích (đặc biệt liên quan đến việc gửi dữ liệu vào và nhận các sự kiện từ các thành phần động). Tóm lại, bạn sẽ cần:

Bước 1) Cập nhật của bạn AppModule

Nhập khẩu PortalModule từ @angular/cdk/portalgói và đăng ký (các) thành phần động của bạn bên trongentryComponents

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

Bước 2. Tùy chọn A: Nếu bạn KHÔNG cần truyền dữ liệu vào và nhận sự kiện từ các thành phần động của mình :

@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{
}

Xem nó trong hành động

Bước 2. Tùy chọn B: Nếu bạn cần truyền dữ liệu vào và nhận các sự kiện từ các thành phần động của bạn :

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

Xem nó trong hành động


1
Anh bạn, đóng đinh. Điều này sẽ được chú ý. Tôi không thể tin rằng khó khăn đến mức nào khi thêm một thành phần động đơn giản trong Angular cho đến khi tôi cần làm một cái. Nó giống như thực hiện thiết lập lại và quay lại thời kỳ tiền JQuery.
Gi1ber7

2
@ Gi1ber7 Tôi biết phải không? Tại sao nó lại mất nhiều thời gian như vậy?
Stephen Paul

1
Cách tiếp cận đẹp, nhưng bạn có biết làm thế nào để truyền tham số cho ChildComponent?
Snook

1
@Snook điều này có thể trả lời stackoverflow.com/questions/47469844/
Stephen Paul

4
@StephenPaul Cách Portaltiếp cận này khác với ngTemplateOutletngComponentOutletnhư thế nào? 🤔
Glenn Mohammad

18

Tôi quyết định thu gọn mọi thứ tôi học được vào một tập tin . Có rất nhiều thứ để đưa vào đây đặc biệt là so với trước RC5. Lưu ý rằng tệp nguồn này bao gồm AppModule và 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

Tôi có một ví dụ đơn giản để chỉ ra cách làm thành phần động 2 góc RC6.

Giả sử, bạn có một mẫu html động = template1 và muốn tải động, trước tiên hãy bọc thành phần

@Component({template: template1})
class DynamicComponent {}

ở đây template1 dưới dạng html, có thể chứa thành phần ng2

Từ RC6, phải có @NgModule bọc thành phần này. @NgModule, giống như mô-đun trong anglarJS 1, nó tách rời một phần khác nhau của ứng dụng ng2, vì vậy:

@Component({
  template: template1,

})
class DynamicComponent {

}
@NgModule({
  imports: [BrowserModule,RouterModule],
  declarations: [DynamicComponent]
})
class DynamicModule { }

(Ở đây nhập RouterModule như trong ví dụ của tôi, có một số thành phần tuyến đường trong html của tôi như bạn có thể thấy sau này)

Bây giờ bạn có thể biên dịch DynamicModule thành: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

Và chúng tôi cần đặt ở trên trong app.moudule.ts để tải nó, vui lòng xem app.moudle.ts của tôi. Để biết thêm chi tiết và đầy đủ, hãy kiểm tra: https://github.com/Longfld/DOUNDicalRouter/blob/master/app/MyRouterLink.ts và app.moudle.ts

và xem bản demo: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview


3
Vì vậy, bạn đã khai báo module1, module2, module3. Và nếu bạn sẽ cần một nội dung mẫu "động" khác, bạn sẽ cần tạo một định dạng (tệp) mẫu moudle4 (module4.ts), phải không? Nếu có, điều đó dường như không năng động. Nó là tĩnh phải không? Hay tôi bỏ lỡ điều gì?
Radim Köhler

Ở trên "template1" là chuỗi html, bạn có thể đặt bất cứ thứ gì vào đó và chúng tôi gọi đây là mẫu động, vì câu hỏi này đang được hỏi
Long Field

6

Trong góc 7.x tôi đã sử dụng các yếu tố góc cho việc này.

  1. Cài đặt @ angular-yếu tố npm i @ angular / yếu tố -s

  2. Tạo dịch vụ phụ kiện.

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

Lưu ý rằng thẻ phần tử tùy chỉnh của bạn phải khác với bộ chọn thành phần góc. trong AppUserIconComponent:

...
selector: app-user-icon
...

và trong trường hợp này tên thẻ tùy chỉnh tôi đã sử dụng "biểu tượng người dùng".

  1. Sau đó, bạn phải gọi đăng ký trong AppComponent:
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
  1. Và bây giờ ở bất kỳ nơi nào trong mã của bạn, bạn có thể sử dụng nó như thế này:
dynamicComponents.create('user-icon', {user:{...}});

hoặc như thế này:

const html = `<div class="wrapper"><user-icon class="user-icon" user='${JSON.stringify(rec.user)}'></user-icon></div>`;

this.content = this.domSanitizer.bypassSecurityTrustHtml(html);

(trong mẫu):

<div class="comment-item d-flex" [innerHTML]="content"></div>

Lưu ý rằng trong trường hợp thứ hai, bạn phải truyền các đối tượng bằng JSON.opesify và sau đó phân tích lại nó. Tôi không thể tìm ra giải pháp tốt hơn.


Cách tiếp cận can thiệp, nhưng bạn sẽ cần nhắm mục tiêu es2015 (vì vậy không hỗ trợ IE11) trong tsconfig.json của bạn, vì vậy, nó sẽ thất bại tạidocument.createElement(tagName);
Snook

Xin chào, như bạn đã đề cập một cách để xử lý đầu vào, vậy đầu ra của các thành phần con có thể được xử lý như thế này không?
Mustahsan

5

Đã giải quyết vấn đề này trong phiên bản Angular 2 Final chỉ bằng cách sử dụng chỉ thị DynamicComponent từ ng-Dynamic .

Sử dụng:

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

Trong đó mẫu là mẫu động và ngữ cảnh của bạn có thể được đặt thành bất kỳ mô hình dữ liệu động nào mà bạn muốn mẫu của mình liên kết.


Tại thời điểm viết Angular 5 với AOT không hỗ trợ điều này vì trình biên dịch JIT không được bao gồm trong gói. Không có AOT, nó hoạt động như một bùa mê :)
Richard Houltz

Điều này vẫn áp dụng cho 7+ góc?
Carlos E

4

Tôi muốn thêm một vài chi tiết trên đầu bài rất xuất sắc này của Radim.

Tôi lấy giải pháp này và làm việc với nó một chút và nhanh chóng gặp phải một số hạn chế. Tôi sẽ chỉ phác thảo những cái đó và sau đó đưa ra giải pháp cho điều đó.

  • Trước hết tôi không thể hiển thị chi tiết động bên trong một chi tiết động (về cơ bản là lồng các UI động vào nhau).
  • Vấn đề tiếp theo là tôi muốn kết xuất một chi tiết động bên trong một trong những phần được tạo sẵn trong giải pháp. Điều đó cũng không thể xảy ra với giải pháp ban đầu.
  • Cuối cùng, không thể sử dụng URL mẫu trên các phần động như trình soạn thảo chuỗi.

Tôi đã đặt một câu hỏi khác dựa trên bài đăng này, về cách đạt được những hạn chế này, có thể tìm thấy ở đây:

biên dịch mẫu đệ quy động trong angular2

Tôi sẽ chỉ phác thảo các câu trả lời cho những hạn chế này, nếu bạn gặp phải vấn đề tương tự như tôi, vì điều đó làm cho giải pháp khá linh hoạt. Nó sẽ là tuyệt vời để có plunker ban đầu được cập nhật với điều đó là tốt.

Để cho phép lồng chi tiết động vào nhau, bạn cần thêm DynamicModule.forRoot () trong câu lệnh nhập trong type.builder.ts

protected createComponentModule (componentType: any) {
    @NgModule({
    imports: [
        PartsModule, 
        DynamicModule.forRoot() //this line here
    ],
    declarations: [
        componentType
    ],
    })
    class RuntimeComponentModule
    {
    }
    // a module for just this Type
    return RuntimeComponentModule;
}

Bên cạnh đó, không thể sử dụng <dynamic-detail>bên trong một trong các phần là trình soạn thảo chuỗi hoặc trình soạn thảo văn bản.

Để cho phép bạn sẽ cần phải thay đổi parts.module.tsdynamic.module.ts

Bên trong parts.module.tsBạn sẽ cần thêm DynamicDetailvàoDYNAMIC_DIRECTIVES

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

Ngoài ra, trong phần dynamic.module.tsbạn phải xóa DynamicDetail vì giờ đây chúng là một phần của các phần

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

Một plunker đã được sửa đổi có thể được tìm thấy ở đây: http://plnkr.co/edit/UYnQHF?p=preview (Tôi không giải quyết vấn đề này, tôi chỉ là người đưa tin :-D)

Cuối cùng, không thể sử dụng các mẫu trong các phần được tạo trên các thành phần động. Một giải pháp (hoặc cách giải quyết. Tôi không chắc đó là lỗi góc hay sử dụng sai khung công tác) là tạo trình biên dịch trong hàm tạo thay vì tiêm nó.

    private _compiler;

    constructor(protected compiler: RuntimeCompiler) {
        const compilerFactory : CompilerFactory =
        platformBrowserDynamic().injector.get(CompilerFactory);
        this._compiler = compilerFactory.createCompiler([]);
    }

Sau đó, sử dụng _compilerđể biên dịch, sau đó templateUrl cũng được bật.

return new Promise((resolve) => {
        this._compiler
            .compileModuleAndAllComponentsAsync(module)
            .then((moduleWithFactories) =>
            {
                let _ = window["_"];
                factory = _.find(moduleWithFactories.componentFactories, { componentType: type });

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });

Hy vọng điều này sẽ giúp người khác!

Trân trọng Morten


4

Theo dõi câu trả lời tuyệt vời của Radmin, có một chút tinh chỉnh cần thiết cho tất cả những ai đang sử dụng phiên bản angular-cli 1.0.0-beta.22 trở lên.

COMPILER_PROVIDERSkhông còn có thể được nhập (để biết chi tiết, xem GitHub angular-cli ).

Vì vậy, cách giải quyết là không sử dụng COMPILER_PROVIDERSJitCompilertrong providersphần này, mà sử dụng JitCompilerFactorytừ '@ angular / trình biên dịch' thay vì như thế này trong lớp trình tạo kiểu:

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

Như bạn có thể thấy, nó không phải là thuốc tiêm và do đó không có sự phụ thuộc với DI. Giải pháp này cũng sẽ làm việc cho các dự án không sử dụng angular-cli.


1
Tuy nhiên, cảm ơn về đề xuất này, tôi đang chạy vào "Không tìm thấy siêu dữ liệu NgModule cho 'DynamicHtmlModule'". Việc triển khai của tôi dựa trên stackoverflow.com/questions/40060498/ từ
Cybey

2
Bất cứ ai cũng đã làm việc JitCompiletFactory với mẫu AOT? Tôi có lỗi tương tự như @Cybey
user2771738


2

Bản thân tôi đang cố gắng xem làm thế nào tôi có thể cập nhật RC4 lên RC5 và do đó tôi tình cờ thấy mục này và cách tiếp cận mới để tạo thành phần động vẫn còn một chút bí ẩn đối với tôi, vì vậy tôi không đề xuất bất cứ điều gì về trình phân giải nhà máy thành phần.

Nhưng, những gì tôi có thể đề xuất là một cách tiếp cận rõ ràng hơn một chút để tạo thành phần trong kịch bản này - chỉ cần sử dụng chuyển đổi trong mẫu sẽ tạo trình soạn thảo chuỗi hoặc trình soạn thảo văn bản theo một số điều kiện, như sau:

<form [ngSwitch]="useTextarea">
    <string-editor *ngSwitchCase="false" propertyName="'code'" 
                 [entity]="entity"></string-editor>
    <text-editor *ngSwitchCase="true" propertyName="'code'" 
                 [entity]="entity"></text-editor>
</form>

Và nhân tiện, biểu thức "[" trong [prop] có nghĩa, điều này biểu thị ràng buộc dữ liệu một chiều, do đó bạn có thể và thậm chí nên bỏ qua những trường hợp đó nếu bạn biết rằng bạn không cần liên kết thuộc tính với biến.


1
Đó sẽ là một cách để đi .. nếu switch/ casechứa một vài quyết định. Nhưng hãy tưởng tượng rằng mẫu được tạo có thể rất lớn ... và khác nhau đối với mỗi thực thể, khác nhau về bảo mật, khác nhau theo trạng thái thực thể, theo từng loại thuộc tính (số, ngày, tham chiếu ... trình soạn thảo) ... Trong trường hợp đó, giải quyết điều này trong mẫu html bằng cách ngSwitchtạo ra một htmltệp lớn, rất rất lớn .
Radim Köhler

Oh tôi đồng ý với bạn. Tôi có loại kịch bản này ngay tại đây, ngay bây giờ khi tôi đang cố tải một thành phần chính của ứng dụng mà không biết trước khi biên dịch lớp cụ thể sẽ được hiển thị. Mặc dù trường hợp cụ thể này không cần tạo thành phần động.
zii

1

Đây là ví dụ về các điều khiển Form động được tạo từ máy chủ.

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

Ví dụ này là các điều khiển biểu mẫu động nằm trong thành phần thêm (Đây là nơi bạn có thể lấy Formcontrols từ máy chủ). Nếu bạn thấy phương thức thành phần, bạn có thể thấy Điều khiển biểu mẫu. Trong ví dụ này tôi không sử dụng vật liệu góc, nhưng nó hoạt động (tôi đang sử dụng @ work). Đây là mục tiêu đến góc 6, nhưng hoạt động trong tất cả các phiên bản trước.

Cần thêm JITComplierFactory cho AngularVersion 5 trở lên.

Cảm ơn

Vijay


0

Đối với trường hợp cụ thể này, có vẻ như sử dụng một lệnh để tự động tạo thành phần sẽ là một lựa chọn tốt hơn. Thí dụ:

Trong HTML nơi bạn muốn tạo thành phần

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

Tôi sẽ tiếp cận và thiết kế chỉ thị theo cách sau.

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

Vì vậy, trong văn bản thành phần của bạn, chuỗi, ngày, bất cứ điều gì - bất kể cấu hình nào bạn đã chuyển trong HTML trong ng-containerphần tử sẽ có sẵn.

Cấu hình, yourConfigcó thể giống nhau và xác định siêu dữ liệu của bạn.

Tùy thuộc vào cấu hình hoặc loại đầu vào của bạn, lệnh sẽ hành động tương ứng và từ các loại được hỗ trợ, nó sẽ hiển thị thành phần thích hợp. Nếu không nó sẽ đăng nhập một lỗi.


-1

Dựa trên câu trả lời của Ophir Stern, đây là một biến thể hoạt động với AoT trong Angular 4. Vấn đề duy nhất tôi có là tôi không thể tiêm bất kỳ dịch vụ nào vào DynamicComponent, nhưng tôi có thể sống với điều đó.

lưu ý: Tôi chưa thử nghiệm với 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;

  }

}

Hi vọng điêu nay co ich.

Chúc mừng!

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.