Angular 2 Component Injection (From the Desk of an Angular 2 Noob)

Mike BerrymanAngular 2 officially released last week – 9/14/16 – which was excellent timing for me as I had just been put on a new project that would be utilizing Angular 2 (at the client’s request).  After spending a few days familiarizing myself with Angular 2 I dived right into the project.

As an aside, and to jump on the bandwagon… why is this called Angular 2?!  I had to forget more or less everything I learned in Angular 1 when transitioning to Angular 2.  It might as well have been called Componentular or something for how much Angular 2 has in common with Angular 1.

Anyway, my first task in my first official Angular 2 project was to create a simple CRUD form for the user to update information stored in a database.  Creating the form itself was pretty easy, but when is a CRUD form just a simple CRUD form?  Since this was a new project we hadn’t yet implemented various useful pieces of functionality, such as a full-screen spinner to block the UI when the user needs to wait for an Asynchronous process to complete.

So I needed to create a relatively easy way to block the entire screen with some sort of overlay and show a spinner or some other working indicator so the user knows something’s happening and to wait a moment.  Essentially I wanted to create a singleton service that would inject a component into the DOM and remove it when done.

The “Spinny” component is relatively easy itself.  I created this component using this stackoverflow post as a starting point, but broke the HTML into its own html page and wrapped the entire thing in a module.  Then I needed to create the service in order to dynamically add/remove the Spinny component to the current page… the stackoverflow link above has an example of a service to do this but despite the post being only 6 months old (as of this writing), Angular 2 had since “evolved” such that some critical functionality used by that version of the service had been deprecated.  Specifically, the DynamicComponentLoader is no longer available in Angular 2.  This example service was not going to work.

With Angular 2 being so fresh and new there are very few examples out there on how to dynamically add a component to the page with the official Angular 2 release.  So I basically needed to figure out how to do this, and do so with only having about a week’s worth of Angular 2 experience.  Here’s what I came up with (with the full disclaimer that this might not be the “best” way):

import { Injectable, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
import { SpinnyComponent } from './spinny.component';

@Injectable()
export class SpinnyService {
  currentSpinny: any = null;
  public defaultViewContainer: ViewContainerRef;
  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  }

  public start() {
    if (this.currentSpinny === null) {
      let spinnyCompFactory = this.componentFactoryResolver.resolveComponentFactory(SpinnyComponent);
      this.currentSpinny = this.defaultViewContainer.createComponent(spinnyCompFactory);
    }
  }

  public stop() {
    if (this.currentSpinny !== null) {
      this.currentSpinny.destroy();
      this.currentSpinny = null;
    }
  }
}

It’s very simple once you know what to do.  Basically the service is using the ComponentFactoryResolver to fetch an instance of the SpinnyComponentFactory (which will in turn create the SpinnyComponent instance to be added to the page), then calls the createComponent method on the ViewContainerRef.  I couldn’t figure out a way to get the ViewContainerRef for the entire app from inside the service, so unfortunately the consumer component needs to set the defaultViewContainer before this service can be used.  It’s annoying but it works.

import { Component, ViewContainerRef } from '@angular/core';
import { SpinnyService } from "../../shared/spinny/spinny.service";

@Component({
  selector: 'my-selector',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.less']
})
export class MyComponent {
  constructor(private vcRef: ViewContainerRef, private spinnyService: SpinnyService) {
    spinnyService.defaultViewContainer = vcRef;
  }
}

Another critical piece to get the dynamic construction of a component to work is to add the component to the entryComponents property of the NgModule decorator.  If this is not done then when the ComponentFactoryResolver tries to get the factory for the SpinnyComponent, it will throw an error.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SpinnyService } from "./spinny.service";
import { SpinnyComponent } from './spinny.component';

@NgModule({
  imports: [CommonModule],
  exports: [],
  declarations: [SpinnyComponent],
  entryComponents: [SpinnyComponent], //THIS IS IMPORTANT!!!!
  providers: [SpinnyService]
})
export class SpinnyModule { }

Notice that I also have the Spinny service listed in the providers for the module.  This is so angular will create and inject a new instance of the Spinny service automatically the first time it’s used, and re-use that instance any subsequent time.

From there I inject the module into the app.module (or whatever the root module is) so it’s available globally and use it like so:

doAsyncWork(): void {
  this.spinnyService.start();
  setTimeout(() => {
    this.spinnyService.stop();
  }, 2000);
}

Hopefully this post saves someone else the trouble of piecing together how to do dynamic component injection with the official Angular 2 release.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s