I want to dynamically create 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 message:
ComponentResolver
is deprecated for dynamic compilation. UseComponentFactoryResolver
together with@NgModule/@Component.entryComponents
or ANALYZE_FOR_ENTRY_COMPONENTS provider instead. For runtime compile only, you can also useCompiler.compileComponentSync/Async
.
I found this (offical angular2) document
Angular 2 Synchronous Dynamic Component Creation
And understand that I can use either
- Kind of dynamic
ngIf
withComponentFactoryResolver
. If I will pass known components into hosting one 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
<form>
<string-editor
[propertyName]="'code'"
[entity]="entity"
></string-editor>
<string-editor
[propertyName]="'description'"
[entity]="entity"
></string-editor>
...
and in another case this one (string-editor
is replaced with text-editor
)
<form>
<text-editor
[propertyName]="'code'"
[entity]="entity"
></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, 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 solution with the Compiler
AOT and JitCompiler (former RuntimeCompiler)
Would you like to use this features with AOT (ahead of time compilation)? Are you getting:
Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 65:17 in the original .ts file), resolving symbol COMPILER_PROVIDERS in .../node_modules/@angular/compiler/src/compiler.d.ts,
Please, leave your comment, vote here:
Following up on Radmin's excellent answer, there is a little tweak needed for everyone who is using angular-cli version 1.0.0-beta.22 and above.
COMPILER_PROVIDERS
can no longer be imported (for details see angular-cli GitHub).So the workaround there is to not use
COMPILER_PROVIDERS
andJitCompiler
in theproviders
section at all, but useJitCompilerFactory
from '@angular/compiler' instead like this inside the type builder class:As you can see, it is not injectable and thus has no dependencies with the DI. This solution should also work for projects not using angular-cli.
I decided to compact everything I learned into one file. There's a lot to take in here especially compared to before RC5. Note that this source file includes the AppModule and AppComponent.
EDIT - related to 2.3.0 (2016-12-07)
Similar topic is discussed here Equivalent of $compile in Angular 2. We need to use
JitCompiler
andNgModule
. Read more aboutNgModule
in Angular2 here: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 dynamicComponent
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 theDynamicComponent
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
PartsModule:NgModule
(holder of small pieces)DynamicModule:NgModule
, which will contain our dynamic component (and referencePartsModule
dynamically)Component
type (only if template has changed)RuntimeModule:NgModule
. This module will contain the previously createdComponent
typeJitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
to getComponentFactory
DynamicComponent
- job of the View Target placeholder andComponentFactory
@Inputs
to new instance (switch fromINPUT
toTEXTAREA
editing), consume@Outputs
NgModule
We need an
NgModule
s.There will be one module for all small components, e.g.
string-editor
,text-editor
(date-editor
,number-editor
...)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()
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:
Read (do read) much more about NgModule there:
A template builder
In our example we will process detail of this kind of entity
To create a
template
, in this plunker we use this simple/naive builder.A trick here is - it builds a template which uses some set of known properties, e.g.
entity
. Such property(-ies) must be part of dynamic component, which we will create next.To make it a bit more easier, we can use an interface to define properties, which our Template builder can use. This will be implemented by our dynamic Component type.
A
ComponentFactory
builderVery important thing here is to keep in mind:
So, we are touching the core of our solution. The Builder, will 1) create
ComponentType
2) create itsNgModule
3) compileComponentFactory
4) cache it for later reuse.An dependency we need to receive:
And here is a snippet how to get a
ComponentFactory
:And here are two methods, which represent the really cool way how to create a decorated classes/types in runtime. Not only
@Component
but also the@NgModule
Important:
ComponentFactory
used by hosting componentFinal piece is a component, which hosts the target for our dynamic component, e.g.
<div #dynamicContentPlaceHolder></div>
. We get a reference to it and useComponentFactory
to create a component. That is in a nutshell, and here are all the pieces of that component (if needed, open plunker here)Let's firstly summarize import statements:
We just receive, template and component builders. Next are properties which are needed for our example (more in comments)
In this simple scenario, our hosting component does not have any
@Input
. So it does not have to react to changes. But despite of that fact (and to be ready for coming changes) - we need to introduce some flag if the component was already (firstly) initiated. And only then we can start the magic.Finally we will use our component builder, and its just compiled/cached
ComponentFacotry
. Our Target placeholder will be asked to instantiate theComponent
with that factory.small extension
Also, we need to keep a reference to compiled template.. to be able properly
destroy()
it, whenever we will change it.done
That is pretty much it. Do not forget to Destroy anything what was built dynamically (ngOnDestroy). Also, be sure to cache dynamic
types
andmodules
if the only difference is their template.Check it all in action here
I have a simple example to show how to do angular 2 rc6 dynamic component.
Say, you have a dynamic html template = template1 and want to dynamic load, firstly wrap into component
here template1 as html, may be contains ng2 component
From rc6, have to have @NgModule wrap this component. @NgModule, just like module in anglarJS 1, it decouple different part of ng2 application, so:
(Here import RouterModule as in my example there is some route components in my html as you can see later on)
Now you can compile DynamicModule as:
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))
And we need put above in app.moudule.ts to load it, please see my app.moudle.ts. For more and full details check: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts and app.moudle.ts
and see demo: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview
Solved this in Angular 2 Final version simply by using the dynamicComponent directive from ng-dynamic.
Usage:
Where template is your dynamic template and context can be set to any dynamic datamodel that you want your template to bind to.
I myself am trying to see how can I update RC4 to RC5 and thus I stumbled upon this entry and new approach to dynamic component creation still holds a bit of mystery to me, so I wont suggest anything on component factory resolver.
But, what I can suggest is a bit clearer approach to component creation on this scenario - just use switch in template that would create string editor or text editor according to some condition, like this:
And by the way, "[" in [prop] expression have a meaning, this indicates one way data binding, hence you can and even should omit those in case if you know that you do not need to bind property to variable.