Injecting components dynamically in Angular 2

When RC-5 was released last week, a lot of things were marked deprecated and breaking changes were introduced.

One of those changes were the way injecting components dynamically is done. This has changed a lot over the past year or so. From deprecating DynamicComponentLoader in favor of directly using ComponentResolver to today’s ComponentFactoryResolver.

Of course, there are more ways to inject components dynamically, I just find this method the most straightforward and simple to understand.

Since Angular 2 now has modules, we will create a couple ourselves.

@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}

This will be our “main module”. And since a module does not display anything by its own we need a component for that.

@Component({
selector: 'my-app',
template: `
<div>Hello, world!</div>
`
})
export class AppComponent {
}

I hope you are familiar with those two by now. If NgModule still seems fuzzy to you (and you have reason for it, believe me) I recommend you read this

Let’s play a little more with the module, just to be sure you understand it better:

@NgModule({
declarations: [HomeComponent],
exports: [HomeComponent]
})
export class HomeModule {
}

And now another component:

@Component({
selector: 'home',
template: `
<div>This is home</div>
`
})
export class HomeComponent {
}

We created a new module for our app: HomeModule. Do this so you don’t pollute your main module with more than necessary. To use our new HomeModule together with the main module we need to import it there:

@NgModule({
imports: [BrowserModule, HomeModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}

By doing this we basically say that the AppModule depends on HomeModule. Enough with NgModule, let’s get back to loading components dynamically.

Going back to our HomeModule, where we will do the actual loading of components, we need to create the component which we will inject. Let’s call it HelloWorldComponent:

@Component({
selector: 'hello-world',
template: `
<div>
Hello, world!, {{name}}
The answer is: {{getAnswer()}}
</div>
`
})
export class HelloWorldComponent implements AfterViewInit {
private name:string = 'You';

constructor(private helloWorldService: HelloWorldService) {
}

ngAfterViewInit(): void {
this.name = 'Me';
}

private getAnswer() {
return this.helloWorldService.giveMeTheAnswer();
}
}

We make it implement the AfterViewInit lifecycle hook so we see those also work for dynamically created components. The same goes with injecting the HelloWorldService:

@Injectable()
export class HelloWorldService {
private answerToEverything:number = 42; // of course

giveMeTheAnswer():number {
return this.answerToEverything;
}
}

And the new HomeModule with needed declarations:

@NgModule({
declarations: [HomeComponent, HelloWorldComponent],
providers: [HelloWorldService],
exports: [HomeComponent]
})
export class HomeModule {
}

Let’s see now what we need to actually inject a component dynamically:

@Component({
selector: 'home',
template: `
<button (click)="sayHello()">Say hello</button>
<div>This is home</div>
`
})
export class HomeComponent {
constructor(private componentFactoryResolver: ComponentFactoryResolver,
private viewContainerRef: ViewContainerRef) {
}

private sayHello() {
const factory = this.componentFactoryResolver.resolveComponentFactory(HelloWorldComponent);
const ref = this.viewContainerRef.createComponent(factory);
ref.changeDetectorRef.detectChanges();
}
}
  • ComponentFactoryResolver — service used to get the factory of the component we want to inject
  • ViewContainerRef — the container of the parent of our component.
  • createComponent(factory) is the part that actually injects our component into the viewContainerRef that calls it.
  • ref — Reference of the component we just injected. We call detectChanges on it so angular will call the necessary lifecycle hooks and start the change detection mechanism.

sayHello() will be used to inject a new instance of our component each time we press the ‘Say hello’ button.

We think now everything is fine and we are ready to inject our HelloWorldComponent. We start our app and everything looks good in the beginning:

A view of our app

Unfortunately, when we try to actually insert something dynamically, we get this beautiful error:

Error, because we don’t have a factory for HelloWorldComponent

This happens because we forgot to add the HelloWorldComponent as an “EntryComponent”. We need this so angular knows which components can be lazy loaded in the future and can create factories for them:

@NgModule({
declarations: [HomeComponent, HelloWorldComponent],
entryComponents: [HelloWorldComponent],
providers: [HelloWorldService],
exports: [HomeComponent]
})
export class HomeModule {
}

Let’s see now how this works:

Finally it works!

Hope this helps someone understand injection of components better. Here is the repository containing the code presented: here.