HiveBrain v1.2.0
Get Started
← Back to all entries
snippettypescriptangularCriticalCanonical

How can I use/create dynamic template to compile dynamic Component with Angular 2.0?

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
angularwithhowcompilecomponentdynamicusetemplatecancreate

Problem

I want to dynamically create a template. This should be used to build a ComponentType at runtime and place (even replace) it somewhere inside of the hosting Component.

Until RC4 I was using ComponentResolver, but with RC5 I get the following message:

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.


I found this document (Angular 2 Synchronous Dynamic Component Creation)

And understand that I can use either

  • Kind of dynamic ngIf with ComponentFactoryResolver. If I pass known components inside of @Component({entryComponents: [comp1, comp2], ...}) - I can use .resolveComponentFactory(componentToRender);



  • Real runtime compilation, with Compiler...



But the question is how to use that Compiler? The note above says that I should call: Compiler.compileComponentSync/Async - so how?

For example. I want to create (based on some configuration conditions) this kind of template for one kind of settings




...


and in another case this one (string-editor is replaced with text-editor)



...


And so on (different number/date/reference editors by property types, skipped some properties for some users...). i.e. this is an example, the real configuration could generate much more different and complex templates.

The template is changing, so I cannot use ComponentFactoryResolver and pass existing ones... I need a solution with the Compiler.

Solution

EDIT - related to 2.3.0 (2016-12-07)

NOTE: to get solution for previous version, check the history of this post

Similar topic is discussed here Equivalent of $compile in Angular 2. We need to use JitCompiler and NgModule. Read more about NgModule in Angular2 here:

  • Angular 2 RC5 - NgModules, Lazy Loading and AoT compilation



In a Nutshell

There is a working plunker/example (dynamic template, dynamic component type, dynamic module,JitCompiler, ... in action)

The principal is:

1) create Template

2) find ComponentFactory in cache - go to 7)

3) - create Component

4) - create Module

5) - compile Module

6) - return (and cache for later use) ComponentFactory

7) use Target and ComponentFactory to create an Instance of dynamic Component

Here is a code snippet (more of it here) - Our custom Builder is returning just built/cached ComponentFactory and the view Target placeholder consume to create an instance of the DynamicComponent

// here we get a TEMPLATE with dynamic content === TODO
  var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);

  // here we get Factory (just compiled or from cache)
  this.typeBuilder
      .createComponentFactory(template)
      .then((factory: ComponentFactory) =>
    {
        // 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;
        //...
    });


This is it - in nutshell it. To get more details.. read below

.
TL&DR

Observe a plunker and come back to read details in case some snippet requires more explanation

.
Detailed explanation - Angular2 RC6++ & runtime components

Below description of this scenario, we will

  • create a module PartsModule:NgModule (holder of small pieces)



  • create another module DynamicModule:NgModule, which will contain our dynamic component (and reference PartsModule dynamically)



  • create dynamic Template (simple approach)



  • create new Component type (only if template has changed)



  • create new RuntimeModule:NgModule. This module will contain the previously created Component type



  • call JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) to get ComponentFactory



  • create an Instance of the DynamicComponent - job of the View Target placeholder and ComponentFactory



  • assign @Inputs to new instance (switch from INPUT to TEXTAREA editing), consume @Outputs



NgModule

We need an NgModules.

While I would like to show a very simple example, in this case, I would need three modules (in fact 4 - but I do not count the AppModule). Please, take this rather than a simple snippet as a basis for a really solid dynamic component generator.

There will be one module for all small components, e.g. string-editor, text-editor (date-editor, number-editor...)

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


Where DYNAMIC_DIRECTIVES are extensible and are intended to hold all small parts used for our dynamic Component template/type. Check app/parts/parts.module.ts

The second will be module for our Dynamic stuff handling. It will contain hosting components and some providers.. which will be singletons. Therefor we will publish them standard way - with forRoot()

import { DynamicDetail }          from './detail.view';
import { DynamicTypeBuilder }     from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';

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

export class DynamicModule {

    static forRoot()
    {
        return {
            ngModule: DynamicModule,
            providers: [ // singletons accross the whole app
              DynamicTemplateBuilder,
              DynamicTypeBuilder
            ], 
        };
    }
}


Check the usage of the forRoot() in the AppModule

Finally, we will need an adhoc, runtime module.. but that will be created later, as a part of DynamicTypeBuilder job.

The forth module, application module, is the one who keeps declares compiler providers:

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


Read (do read) much more about NgModule there:

  • Angular 2 RC5 - NgModules, Lazy Loading and AoT com

Code Snippets

// 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;
        //...
    });
@NgModule({
  imports:      [ 
      CommonModule,
      FormsModule
  ],
  declarations: [
      DYNAMIC_DIRECTIVES
  ],
  exports: [
      DYNAMIC_DIRECTIVES,
      CommonModule,
      FormsModule
  ]
})
export class PartsModule { }
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
            ], 
        };
    }
}
...
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
  ],
entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
};

Context

Stack Overflow Q#38888008, score: 174

Revisions (0)

No revisions yet.