I am trying to implement Dynamic Component using following code-
createWidget(template: string) {
const html = template;
if (!html) return;
if (this.cmpRef) {
this.cmpRef.destroy();
}
const compMetadata = new Component({
selector: 'dynamic-html',
template: html
});
this.createComponentFactory( this.compiler, compMetadata )
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
console.log("Injector: " + injector);
this.vcRef.createComponent(factory, 0, injector, []);
console.log( "Create Widget End.." );
});
}
createComponentFactory( compiler: Compiler, metadata: Component) {
@Component( metadata )
class DynamicComponent { };
@NgModule({
imports: [ CommonModule, FormsModule ],
declarations: [ DynamicComponent ]
})
class DynamicHtmlModule { }
return compiler.compileModuleAndAllComponentsAsync( DynamicHtmlModule )
.then(( moduleWithComponentFactory: ModuleWithComponentFactories<any> ) => {
return moduleWithComponentFactory.componentFactories.find( x => x.componentType === DynamicComponent );
});
}
the code is running fine when I am calling createWidget method using-
this.createWidget('Angular2');
But problem is that when I am calling createWidget multiple times using for loop, the template which generated is on reverse ordered. Please see my plunker https://plnkr.co/edit/hqKYsGlyuk78tg61zjfd?p=preview
In this plunker example, Angular2 is displaying before than Angular1.
You have two choices :
1- Create the modules Asyncrounously, which you're already doing and for that to make sure your components will appear in the DOM in the same order as your for loop , you need to use your loop index like this:
for(var i = 1; i <= 2; i++) {
this.createWidget('<h1>Angular' + i + '</h1>' , i-1); // minus 1 because you're starting from 1 !!!!!
}
createWidget(template: string , index) {
const html = template;
if (!html) return;
if (this.cmpRef) {
this.cmpRef.destroy();
}
const compMetadata = new Component({
selector: 'dynamic-html',
template: html
});
this.createComponentFactory( this.compiler, compMetadata )
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.createComponent(factory, index, injector, []);
});
}
Because inside the loop there is a async function ( createComponentFactory ) , so you can never guarantee that the first component is always created first and second is second because the compilation for the first could take more than second and then second would be resolved first .
For this scenarios, it's more concrete and trustable to pass the index and make sure you're putting them in the same order of your for loop.
Here is your plunker ,working fine.
https://plnkr.co/edit/VmmwzzrcJq6En5tXcyl6?p=preview
2- To create the modules Syncronously, and then you can trust that they will always appear in order without defining the index yourself.
export class App {
name:string;
constructor(public compiler: Compiler, public vcRef: ViewContainerRef) {
this.name = 'Angular2';
for(var i = 1; i <= 2; i++) {
this.createWidget('<h1>Angular' + i + '</h1>');
}
}
createWidget(template: string ) {
const html = template;
if (!html) return;
if (this.cmpRef) {
this.cmpRef.destroy();
}
const compMetadata = new Component({
selector: 'dynamic-html',
template: html
});
let factory = this.createComponentFactory( this.compiler, compMetadata )
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.createComponent(factory, undefined , injector, []);
}
createComponentFactory( compiler: Compiler, metadata: Component ) {
@Component( metadata )
class DynamicComponent { };
@NgModule({
imports: [ CommonModule ],
declarations: [ DynamicComponent ]
})
class DynamicHtmlModule { }
let moduleWithComponentFactory = compiler.compileModuleAndAllComponentsSync( DynamicHtmlModule )
console.log(moduleWithComponentFactory.componentFactories)
return moduleWithComponentFactory.componentFactories.find( x => x.componentType === DynamicComponent );
}
}
https://plnkr.co/edit/9WNrVABMupq1q2YKO5wi?p=preview
You can try to calculate index
for vcRef.createComponent
method like
loadedIndices: number[] = [];
...
let renderedIndex = this.loadedIndices.indexOf(Math.max.apply(Math, this.loadedIndices.filter(x => x <= index))) + 1;
this.vcRef.createComponent(factory, renderedIndex, injector, []);
this.loadedIndices.push(index);
this.loadedIndices.sort((a, b) => a - b);
where index
is the destination index for each of component
Plunker Example
Just remove 0
from
this.vcRef.createComponent(factory, 0, injector, []);
This makes each component being inserted at position 0 (before all others).
Without the explicit index, each component is appended after the ones added before:
this.vcRef.createComponent(factory, {injector: injector, projectableNodes: []});