I'm trying to load dynamically a component in the final release 2.0.0.
Using RC5 I was loading using the following code:
Create a directive to load the controls:
import {
CheckboxComponent, CheckboxListComponent,DatePickerComponent
} from '../components/';
@Directive({
selector: '[ctrl-factory]'
})
export class ControlFactoryDirective implements OnChanges {
@Input() model: any;
constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) {
}
create(cp) {
this.resolver.resolveComponent(cp)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.createComponent(factory, 0, injector, []);
let ch = this.vcRef.createComponent(factory, 0, injector, []).instance;
ch.model = this.model;
});
}
ngOnChanges() {
if (!this.model) return;
switch (this.model.type) {
case 'checkbox':
this.create(CheckboxComponent);
break;
case 'checkboxlist':
this.create(CheckboxListComponent);
break;
case 'datepicker':
this.create(DatePickerComponent);
break;
default:
break;
}
}
}
Then loaded that directive in my page like this:
<div ctrl-factory *ngFor="let child of page.childrens" [model]="child"></div>
But after updating from rc5 to 2.0.0 final release, the resolver doesn't exist anymore, was replaced by compiler.
I found loads of places showing how to load it using different codes, but all those too complex and I couldn't make it work.
Take this for instance: How can I use/create dynamic template to compile dynamic Component with Angular 2.0?
It looks more specific to that scenario, my one I just need to load the component and set an @Input called model.
One thing when I was trying I had to create dynamically a module for each component, then add the component to it. But then I had issues saying that the component was being set in more than one Module, try to remove in some place an not working.
The major part of the code shown, I get from this link: /
And did a couple of changes.
Update
I manage to make it work, using the following approach:
The create method has been changed to
private create(cp) {
@NgModule({
imports: [BrowserModule, ControlsModule],
declarations: []
})
class DynamicModule {}
thispilerpileModuleAndAllComponentsAsync(DynamicModule)
.then(({componentFactories}) => {
const compFactory = componentFactories.find(x => xponentType === cp);
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
const cmpRef = this.vcRef.createComponent(compFactory, 0, injector, []);
cmpRef.instance.model = this.model;
});
}
Most places I've found, set create the Component and set it to the DynamicModule, the issue with that is when you are already declaring that same component in a different module, angular is going to complain about. The solution in my case was to import the my ControlsModule that has all my controls being exported.
I'm trying to load dynamically a component in the final release 2.0.0.
Using RC5 I was loading using the following code:
Create a directive to load the controls:
import {
CheckboxComponent, CheckboxListComponent,DatePickerComponent
} from '../components/';
@Directive({
selector: '[ctrl-factory]'
})
export class ControlFactoryDirective implements OnChanges {
@Input() model: any;
constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) {
}
create(cp) {
this.resolver.resolveComponent(cp)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.createComponent(factory, 0, injector, []);
let ch = this.vcRef.createComponent(factory, 0, injector, []).instance;
ch.model = this.model;
});
}
ngOnChanges() {
if (!this.model) return;
switch (this.model.type) {
case 'checkbox':
this.create(CheckboxComponent);
break;
case 'checkboxlist':
this.create(CheckboxListComponent);
break;
case 'datepicker':
this.create(DatePickerComponent);
break;
default:
break;
}
}
}
Then loaded that directive in my page like this:
<div ctrl-factory *ngFor="let child of page.childrens" [model]="child"></div>
But after updating from rc5 to 2.0.0 final release, the resolver doesn't exist anymore, was replaced by compiler.
I found loads of places showing how to load it using different codes, but all those too complex and I couldn't make it work.
Take this for instance: How can I use/create dynamic template to compile dynamic Component with Angular 2.0?
It looks more specific to that scenario, my one I just need to load the component and set an @Input called model.
One thing when I was trying I had to create dynamically a module for each component, then add the component to it. But then I had issues saying that the component was being set in more than one Module, try to remove in some place an not working.
The major part of the code shown, I get from this link: http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2-rc-5/
And did a couple of changes.
Update
I manage to make it work, using the following approach:
The create method has been changed to
private create(cp) {
@NgModule({
imports: [BrowserModule, ControlsModule],
declarations: []
})
class DynamicModule {}
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
.then(({componentFactories}) => {
const compFactory = componentFactories.find(x => x.componentType === cp);
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
const cmpRef = this.vcRef.createComponent(compFactory, 0, injector, []);
cmpRef.instance.model = this.model;
});
}
Most places I've found, set create the Component and set it to the DynamicModule, the issue with that is when you are already declaring that same component in a different module, angular is going to complain about. The solution in my case was to import the my ControlsModule that has all my controls being exported.
Share Improve this question edited Feb 21, 2018 at 21:18 Roham Tehrani 2,9897 gold badges37 silver badges50 bronze badges asked Sep 24, 2016 at 17:15 AbnerAbner 6222 gold badges9 silver badges24 bronze badges 1- I'm trying to create a similar thing. I have a JSON file that loads classes and their list of lectures. The classes are added to the nav bar and the lectures are added as a list in a detail view. On clicking a lecture, a component should be loaded in the detail view. For each lecture, there is a different component and I was thinking of creating a module or a router link for each lecture (and a router-outlet), instead of just loading a component like your example. I'm still trying to figure out the best way to load things. – Atieh Commented Dec 13, 2016 at 19:32
2 Answers
Reset to default 12Update
NgComponentOutlet was introduced in 4.0.0-beta.3 https://github.com/angular/angular/commit/8578682
Coming soon NgComponentOutlet
I see two options to do that:
1) Using ComponentFactoryResolver.
It uses the already generated factory and the code looks something like this:
constructor(private vcRef: ViewContainerRef, private resolver: ComponentFactoryResolver) { }
create(comp) {
const factory = this.resolver.resolveComponentFactory(comp);
const compRef = this.vcRef.createComponent(factory);
(<any>compRef).instance.model = this.model;
}
In this case we have to define dynamic component in declarations
and entryComponents
properties within decorator of module
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, DynamicComponent ],
entryComponents: [DynamicComponent],
bootstrap: [ AppComponent ]
})
export class AppModule { }
- Dynamically Creating Components With Angular 2.0
- Plunker Example
2) Using Compiler
In this case we can only run module compilation by using compiler.compileModuleAndAllComponentsAsync
and then find component from componentFactories array. Your directive might look like this:
constructor(private vcRef: ViewContainerRef, private loader: DynamicLoaderService) { }
create(comp) {
this.loader.createComponentFactory(comp).then(factory => {
const compRef = this.vcRef.createComponent(factory);
(<any>compRef).instance.model = this.model;
})
}
DynamicLoaderService
is a global service that will load and store component factories.
@Injectable()
export class DynamicLoaderService {
constructor(protected compiler: Compiler) {}
private resolveCompHelper$ = new Subject<any>();
private cache = new Map<string, ComponentFactory<any> | number>();
public createComponentFactory(type: string) : Promise<ComponentFactory<any>> {
let factory = this.cache.get(type);
// if factory has been already loading
if(factory === 1) {
return new Promise((resolve) => {
// waiting compilation of factory
const subscriber = this.resolveCompHelper$.subscribe((data) => {
if(type !== data.type) return;
subscriber.unsubscribe();
resolve(data.factory);
});
});
}
// factory exists in cache
if (factory) {
return new Promise((resolve) => resolve(factory));
}
const comp = typeMap[type];
// factory startes loading
this.cache.set(type, 1);
return new Promise((resolve) => {
this.compiler.compileModuleAndAllComponentsAsync(createComponentModule(comp))
.then((moduleWithFactories: ModuleWithComponentFactories<any>) => {
factory = moduleWithFactories.componentFactories
.find(x => x.componentType === comp);
this.cache.set(type, factory);
this.resolveCompHelper$.next({ type, factory});
resolve(factory);
});
});
}
}
Plunker Example
Hope it helps you!
It was useful for me to see this option based on a Directive, but I found the one that fit my needs based on Components: https://www.ag-grid.com/ag-grid-angular-aot-dynamic-components/
Happy coding!