This guide was written for Angular 2 version: 2.0.0-rc.6
Angular 1.x has relied heavily on module support at the framework level to give us a clear way to organize our application into logical units. With the release of Angular 2 RC5, the concept of framework level support for modules was reintroduced via ngModule
.
The best way to learn about modules in Angular 1.x, is to examine the relationship between a parent module and a child module. Once the pattern is identified, it will repeat itself indefinitely as your application grows.
All non-trivial Angular 1.x applications will bootstrap with a root module within an ng-app
declaration in the main HTML file. In its simplest form, we will call angular.module
and pass in two parameters. The first parameter being a string identifier that we can use to reference our newly created module and an array of dependencies for that module.
If angular.module
is called with the second parameter, it will create a new module with that key regardless if the module has been previously created or not.
angular
.module('app', []); // This is a setter
With that in mind, if angular.module
is called with just the string identifier, it will operate as a getter function and just return the existing module.
angular
.module('app'); // This is a getter
We are going to create a new module called app
and initialize it with zero dependencies to start out with. With our module declared, we will chain on a call to angular.component
to attach AppComponent
to our app
module.
const AppComponent = {
template: `
<h1>Root Component</h1>
`
};
angular
.module('app', [])
.component('app', AppComponent);
To ensure that our application bootstraps with the app
module, we will add ng-app="app"
to our body tag. Within the body
tag, we will also initialize the AppComponent
by adding an app
element to the page.
<body ng-app="app">
<app></app>
</body>
We now have a completely bootstrapped Angular application with a top level module that we can attach various items to.
As an application begins to grow, we will want to organize not only our file structure by feature but also organize it the same way at the framework level. To illustrate this, we are going to introduce a contacts feature that contains a single component. The first step is to declare our contacts
module using the setter syntax and with no dependencies. Then we will attach the ContactsComponent
to that module using the angular.component
method.
const ContactsComponent = {
template: `
<h3>Contacts go here.</h3>
`
};
angular
.module('contacts', [])
.component('contacts', ContactsComponent);
Now that we have a new child module, how do we make it available to our root module? Presuming that our source files are being loading properly, we will go to our app
module declaration and add contacts
to the array of dependencies in the second parameter. This tells Angular to look for the contacts
module when it is initializing the app
module and make all of the contacts
functionality available.
angular
.module('app', ['contacts'])
.component('app', AppComponent);
With the contacts
module now available, we can update the AppComponent
to include a contacts
element within its template.
const AppComponent = {
template: `
<h1>Root Component</h1>
<contacts></contacts>
`
};
angular
.module('app', ['contacts'])
.component('app', AppComponent);
This is a fairly fundamental technique in Angular 1.x being it is an organizational cornerstone for a scalable architecture. What is interesting is that this concept did not exist in Angular 2 until the release of RC5 and this is what we will examine next.
The main organization mechanism is still the component within Angular 2 but ngModule
was introduce to make organizing and connecting components together much easier. To parallel our Angular 1.x example, we will start by defining a top-level AppComponent
that has a selector of app
.
// app.component.ts
import {Component} from '@angular/core';
@Component({
selector: 'app',
providers: [],
template: `
<h1>Root Component</h1>
`
})
export class AppComponent {}
With our AppComponent
created, we will create an Angular module to provide context for our component and define the relationship it has with the rest of the application. An Angular 2 module follows the same pattern as components, directives, injectables, etc in that it is just an ES6 class with metadata to appropriately decorate it.
We have created an empty class called AppModule
that will serve as a placeholder for us to use the @NgModule
decorator. The @NgModule
decorator takes a configuration object that will typically contain imports, component declarations and if it is a top-level module, a reference to the component we want to bootstrap. In the code below, we are importing BrowserModule
because this is the context we want to bootstrap our application in; also, we will declare AppComponent
component and indicate we want to use this as our entry point to bootstrap the module.
// app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent} from './app.component';
@NgModule({
imports: [BrowserModule],
bootstrap: [AppComponent],
declarations: [AppComponent]
})
export class AppModule {}
And instead of bootstrapping our top-level component directly, we will instead bootstrap our top-level module which is then responsible for delegating the implementation details. In this case, we know that when AppModule
is instantiated, that it will in turn, instantiate the AppComponent
.
// main.ts
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
And then within our index.html
file, we will add our top-level component by adding an app
element to our page.
<body>
<app>
loading...
</app>
</body>
As with the first example, we will introduce a contacts feature in the form of a ContactsComponent
with the selector of contacts
.
// contacts.component.ts
import {Component} from '@angular/core';
@Component({
selector: 'contacts',
template: `
<h3>
Contacts go here.
</h3>
`
})
export class ContactsComponent { }
How do we make the ContactsComponent
available to the rest of the application? We accomplish this by adding it to our AppModule
so that any other component within that module can consume it. We will import our ContactsComponent
and then add it to the declarations
array and that is it!
// app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent} from './app.component';
import {ContactsComponent} from './contacts.component';
@NgModule({
imports: [BrowserModule],
bootstrap: [AppComponent],
declarations: [AppComponent, ContactsComponent]
})
export class AppModule {}
This means that we no longer have to manually declare every single dependency at the component level in our Angular 2 application. We can use the ContactsComponent
within our AppComponent
by simply adding the contacts
element to the template.
import {Component} from '@angular/core';
@Component({
selector: 'app',
providers: [],
template: `
<h1>Root Component</h1>
<contacts></contacts>
`
})
export class AppComponent {}
The introduction of NgModule
provides us with a really clean way to wire up features and dependencies that we were used to in Angular 1.x.
Todd Motto (@toddmotto) is a Google Developer Expert from England, UK. He's taught millions of developers world-wide through his blogs, videos, conferences and workshops. He focuses on teaching Angular and JavaScript courses over on Ultimate Courses.