Quick Start with Angular Modules

Angular 2 RC5 was released was released last week. Looking through the change log of bug fixes and new features shows a lot of progress has been made. However, I experienced a sinking feeling after reading several pages of Breaking Changes about the introduction of @NgModule and changing how we need to bootstrap our applications.
To be fair, the Angular team did a good job of providing examples of before and after code as part of those changes but it was still a lot to process. I started to dig through their Developer Guide for NgModule to get my head around the changes and while packed with useful information, it felt dense and intimidating.
Once the initial shock subsided, I decided the best way to see the impact of these changes and the amount of effort required to prepare for RC5 was to try upgrading an application.
I picked a demo application I had built for a presentation that I gave at Angular Camp earlier this year. The process ended up being easier than expected. Take a look at this pull request where each step is its own commit.
This post is meant to be a quick guide on the minimal amount of steps you will need to take to get your applications working with @NgModule.
Updating Dependencies
Firstly, you must update your package.json dependencies to point to RC5 and run npm install (see commit).
Before doing any other changes to my application, I ran the application and fully expected it to crash. To my surprise, the application booted up with only a few deprecation warnings in the console.
In RC5, the Angular 2 team has left many of the deprecated features in place - providing you with warnings instead. Under the hood, they are hoisting things into NgModule for you. This allows developers to benefit from the numerous bug fixes and new features in RC5 while giving them time to refactor their code in preparation for RC6. This removes all of the deprecated API's.
Changing your Bootstrap
Next up, I changed how the application was bootstrapped. The old way of bootstrap(Module,[providers]) is going away and we now need to create an Angular Module.
The old bootstrap code looked like this:
bootstrap(HomePage, [
provideForms(),
NgRedux,
DevToolsExtension,
ACTION_PROVIDERS,
PartyService,
provide(APP_BASE_HREF, { useValue: '/' })
]);
I then needed to create an Angular Module that will be used to bootstrap the application.
An Angular Module is simply a class decorated with the @NgModule decorator - similar to angular.module from Angular 1.x.
The key things we need to do with the Angular Module are:
- Import other modules that will be available within this module;
- Declare the components what we want available within the module;
- Inform which component is the one to be bootstrapped;
- Specify the providers that are to be used.
This ends up being a pretty easy process and the resulting module definition ends up looking like: (see commit)
import { FormsModule} from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } '@angular/core';
@NgModule({
imports: [BrowserModule, FormsModule],
declarations: [HomePage],
bootstrap: [HomePage],
providers: [NgRedux,
DevToolsExtension,
ACTION_PROVIDERS,
PartyService,
{ provide: APP_BASE_HREF, useValue: '/' } ]
})
class MyAppModule { }
We are letting Angular know that for MyAppModule, we want BrowserModule and FormsModule which will be available across the MyAppModule. This gives us access to built-in directives and components like ngFor from BrowserModule, and also the form directives like ngModel, form, etc from the FormsModule.
It is important to note the Modules and Components are scoped within Angular Module you are defining. If you were to create another feature module that required FormsModule, you would need to import FormsModule on that Angular Module definition.
For the final step, bring in platformBrowserDynamic and replace the old bootstrap code.
Before:
bootstrap(HomePage, [
provideForms(),
NgRedux,
DevToolsExtension,
ACTION_PROVIDERS,
PartyService,
provide(APP_BASE_HREF, { useValue: '/' })
]);
After:
platformBrowserDynamic().bootstrapModule(MyAppModule)
At this point the application will still run as expected. But there is still some house cleaning to finish.
Cleaning up Component Imports
One of the complaints I've seen about Angular 2 is the amount of ceremony that can go into telling a component to use another component. You need to import it at the top of your file, then modify your @Component to declare it with the directives property.
Now, with the Angular Module we have told our MyAppModule that forms are available for it. So, to complete the house cleaning we can:
- Find our components that are importing REACTIVE_FORM_DIRECTIVES;
- Remove the import;
- Remove it from the directives declaration for that component.
As you can see in this commit I didn't need to touch many files since this is a small application.
Before:
/* .... */
import { Panel } from '../';
import { REACTIVE_FORM_DIRECTIVES } from '@angular/forms';
@Component({
selector: 'tb-table',
template: TEMPLATE,
directives: [Panel, REACTIVE_FORM_DIRECTIVES ]
})
export class Table {
/* .... */
};
After:
/* .... */
import { Panel } from '../';
@Component({
selector: 'tb-table',
template: TEMPLATE,
directives: [Panel]
})
export class Table {
/* ..... */
};
We're not saving much code but importing and declare them for use quickly gets annoying.
If you were to stop here, you would almost up and running with RC5 and the @NgModule. But let's take things a little bit further and create our own module to group up some of our reusable presentation components.
Modularizing Your Code
So far, we have bootstrapped our application with the new Angular Module and cleaned up some of our component and imports.
Next, let's now look at creating our first Angular Module to encompass a few components.
An obvious starting point is the src/components folder. Currently, we are bundling up and exporting our components like this:
export * from './lineup';
export * from './panel';
export * from './table';
export * from './menu';
export * from './orders';
In our application, it would be nice if we could use components like Lineup, Panel, etc without needing to explicitly import them into every other component that uses them. Creating a module for this is pretty straight forward.
import { NgModule } from '@angular/core';
import { Lineup } from './lineup';
import { Panel } from './panel';
import { Table } from './table';
import { Menu } from './menu';
import { Orders } from './orders';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [CommonModule, FormsModule],
declarations: [Lineup, Panel, Table, Menu, Orders],
exports: [Lineup, Panel, Table, Menu, Orders]
})
export class ComponentsModule {
}
Since we have components that require forms and core browser directives, we need to import here so that our other components can use them.
Then we must declare which components are a part of this package and can be used freely by other components within this module.
Next, we decide which components are publicly available to other parts of the application outside of this component. There could be cases where we have created components that are implementation details of other components but are not intended to be used directly by other parts of the application.
Then, in our main MyAppModule, we want to import our ComponentsModule.
/* .... */
import { ComponentModule } from './components';
@NgModule({
imports: [BrowserModule,
FormsModule,
ComponentsModule],
declarations: [HomePage],
bootstrap: [HomePage],
providers: [
PartyService,
{ provide: APP_BASE_HREF, useValue: '/' }]
})
class MyAppModule { }
Finally, we can go to our container components that were making use of things like Lineup, Panel, etc and remove the imports and directive declarations. Take a look at the full commit
Before:
/* .... */
import { Lineup, Panel, Table, Menu, Orders } from '../components';
@Component({
selector: 'tb-home',
template: TEMPLATE,
directives: [ Lineup, Panel, Table, Menu, Orders ],
encapsulation: ViewEncapsulation.None,
styles: [ require('../styles/index.css') ],
})
export class HomePage {
/* ... */
};
After:
@Component({
selector: 'tb-home',
template: TEMPLATE,
encapsulation: ViewEncapsulation.None,
styles: [ require('../styles/index.css') ],
})
export class HomePage {
/* .... */
};
Now for every Component that is part of our MyAppModule, we can make use of our reusable presentation components like Panel, Table, Lineup, etc without needing to import them and define them in the @Component definition.
I was initially concerned when trying to figure out @NgModule. I thought it was going to take a great deal of effort to upgrade things to at least an 'up and running state'. Fortunately, after spending a few hours with it, I've determined that it's not that bad. I'm already considering the best ways of modularizing my larger applications.
Hopefully this has helped you understand the basics of Angular Modules and @NgModule. For more details, check out the NgModule Developer Guide and the RC4 to RC5 migration guide.