NgModules help organize an application into cohesive blocks of functionality.
An NgModule is a class adorned with the @NgModule decorator function. @NgModule
takes a metadata object that tells Angular how to compile and run module code. It identifies the module's own components, directives, and pipes, making some of them public so external components can use them. @NgModule
may add service providers to the application dependency injectors. And there are many more options covered here.
Before reading this page, read the The Root Module page, which introduces NgModules and the essentials of creating and maintaining a single root AppModule
for the entire application.
This page covers NgModules in greater depth.
This page explains NgModules through a progression of improvements to a sample with a "Tour of Heroes" theme. Here's an index to live examples at key moments in the evolution of the sample:
This page covers NgModule concepts in a tutorial fashion.
The companion NgModule FAQs cookbook offers answers to specific design and implementation questions. Read this page before reading those FAQs.
Modules are a great way to organize an application and extend it with capabilities from external libraries.
Many Angular libraries are modules (such as FormsModule
, HttpModule
, and RouterModule
). Many third-party libraries are available as NgModules (such as Material Design, Ionic, AngularFire2).
NgModules consolidate components, directives, and pipes into cohesive blocks of functionality, each focused on a feature area, application business domain, workflow, or common collection of utilities.
Modules can also add services to the application. Such services might be internally developed, such as the application logger. Services can come from outside sources, such as the Angular router and Http client.
Modules can be loaded eagerly when the application starts. They can also be lazy loaded asynchronously by the router.
An NgModule is a class decorated with @NgModule
metadata. The metadata do the following:
Every Angular app has at least one module class, the root module. You bootstrap that module to launch the application.
The root module is all you need in a simple application with a few components. As the app grows, you refactor the root module into feature modules that represent collections of related functionality. You then import these modules into the root module.
Later in this page, you'll read about this process. For now, you'll start with the root module.
Every Angular app has a root module class. By convention, the root module class is called AppModule
and it exists in a file named app.module.ts
.
The AppModule
from the QuickStart seed on the Setup page is as minimal as possible:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
The @NgModule
decorator defines the metadata for the module. This page takes an intuitive approach to understanding the metadata and fills in details as it progresses.
The metadata imports a single helper module, BrowserModule
, which every browser app must import.
BrowserModule
registers critical application service providers. It also includes common directives like NgIf
and NgFor
, which become immediately visible and usable in any of this module's component templates.
The declarations
list identifies the application's only component, the root component, the top of the app's rather bare component tree.
The example AppComponent
simply displays a data-bound title:
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: '<h1>{{title}}</h1>', }) export class AppComponent { title = 'Minimal NgModule'; }
Lastly, the @NgModule.bootstrap
property identifies this AppComponent
as the bootstrap component. When Angular launches the app, it places the HTML rendering of AppComponent
in the DOM, inside the <my-app>
element tags of the index.html
.
You launch the application by bootstrapping the AppModule
in the main.ts
file.
Angular offers a variety of bootstrapping options targeting multiple platforms. This page describes two options, both targeting the browser.
In the first, dynamic option, the Angular compiler compiles the application in the browser and then launches the app.
// The browser platform with a compiler import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; // The app module import { AppModule } from './app/app.module'; // Compile and launch the module platformBrowserDynamic().bootstrapModule(AppModule);
The samples in this page demonstrate the dynamic bootstrapping approach.
Try the live example.
Consider the static alternative which can produce a much smaller application that launches faster, especially on mobile devices and high latency networks.
In the static option, the Angular compiler runs ahead of time as part of the build process, producing a collection of class factories in their own files. Among them is the AppModuleNgFactory
.
The syntax for bootstrapping the pre-compiled AppModuleNgFactory
is similar to the dynamic version that bootstraps the AppModule
class.
// The browser platform without a compiler import { platformBrowser } from '@angular/platform-browser'; // The app module factory produced by the static offline compiler import { AppModuleNgFactory } from './app/app.module.ngfactory'; // Launch with the app module factory. platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
Because the entire application was pre-compiled, Angular doesn't ship the Angular compiler to the browser and doesn't compile in the browser.
The application code downloaded to the browser is much smaller than the dynamic equivalent and it's ready to execute immediately. The performance boost can be significant.
Both the JIT and AOT compilers generate an AppModuleNgFactory
class from the same AppModule
source code. The JIT compiler creates that factory class on the fly, in memory, in the browser. The AOT compiler outputs the factory to a physical file that is imported here in the static version of main.ts
.
In general, the AppModule
should neither know nor care how it is bootstrapped.
Although the AppModule
evolves as the app grows, the bootstrap code in main.ts
doesn't change. This is the last time you'll look at main.ts
.
As the app evolves, the first addition is a HighlightDirective
, an attribute directive that sets the background color of the attached element.
import { Directive, ElementRef } from '@angular/core'; @Directive({ selector: '[highlight]' }) /** Highlight the attached element in gold */ export class HighlightDirective { constructor(el: ElementRef) { el.nativeElement.style.backgroundColor = 'gold'; console.log( `* AppRoot highlight called for ${el.nativeElement.tagName}`); } }
Update the AppComponent
template to attach the directive to the title:
template: '<h1 highlight>{{title}}</h1>'
If you ran the app now, Angular wouldn't recognize the highlight
attribute and would ignore it. You must declare the directive in AppModule
.
Import the HighlightDirective
class and add it to the module's declarations
like this:
declarations: [ AppComponent, HighlightDirective, ],
Refactor the title into its own TitleComponent
. The component's template binds to the component's title
and subtitle
properties like this:
<h1 highlight>{{title}} {{subtitle}}</h1>
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-title', templateUrl: './title.component.html', }) export class TitleComponent { @Input() subtitle = ''; title = 'Angular Modules'; }
Rewrite the AppComponent
to display the new TitleComponent
in the <app-title>
element, using an input binding to set the subtitle
.
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: '<app-title [subtitle]="subtitle"></app-title>' }) export class AppComponent { subtitle = '(v1)'; }
Angular won't recognize the <app-title>
tag until you declare it in AppModule
. Import the TitleComponent
class and add it to the module's declarations
:
declarations: [ AppComponent, HighlightDirective, TitleComponent, ],
Modules are a great way to provide services for all of the module's components.
The Dependency Injection page describes the Angular hierarchical dependency-injection system and how to configure that system with providers at different levels of the application's component tree.
A module can add providers to the application's root dependency injector, making those services available everywhere in the application.
Many applications capture information about the currently logged-in user and make that information accessible through a user service. This sample application has a dummy implementation of such a UserService
.
import { Injectable } from '@angular/core'; @Injectable() /** Dummy version of an authenticated user service */ export class UserService { userName = 'Sherlock Holmes'; }
The sample application should display a welcome message to the logged-in user just below the application title. Update the TitleComponent
template to show the welcome message below the application title.
<h1 highlight>{{title}} {{subtitle}}</h1> <p *ngIf="user"> <i>Welcome, {{user}}</i> <p>
Update the TitleComponent
class with a constructor that injects the UserService
and sets the component's user
property from the service.
import { Component, Input } from '@angular/core'; import { UserService } from './user.service'; @Component({ selector: 'app-title', templateUrl: './title.component.html', }) export class TitleComponent { @Input() subtitle = ''; title = 'Angular Modules'; user = ''; constructor(userService: UserService) { this.user = userService.userName; } }
You've defined and used the service. Now to provide it for all components to use, add it to a providers
property in the AppModule
metadata:
providers: [ UserService ],
In the revised TitleComponent
, an *ngIf
directive guards the message. There is no message if there is no user.
<p *ngIf="user"> <i>Welcome, {{user}}</i> <p>
Although AppModule
doesn't declare NgIf
, the application still compiles and runs. How can that be? The Angular compiler should either ignore or complain about unrecognized HTML.
Angular does recognize NgIf
because you imported it earlier. The initial version of AppModule
imports BrowserModule
.
imports: [ BrowserModule ],
Importing BrowserModule
made all of its public components, directives, and pipes visible to the component templates in AppModule
.
More accurately, NgIf
is declared in CommonModule
from @angular/common
.
CommonModule
contributes many of the common directives that applications need, including ngIf
and ngFor
.
BrowserModule
imports CommonModule
and re-exports it. The net effect is that an importer of BrowserModule
gets CommonModule
directives automatically.
Many familiar Angular directives don't belong to CommonModule
. For example, NgModel
and RouterLink
belong to Angular's FormsModule
and RouterModule
respectively. You must import those modules before you can use their directives.
To illustrate this point, you'll extend the sample app with ContactComponent
, a form component that imports form support from the Angular FormsModule
.
Angular forms are a great way to manage user data entry.
The ContactComponent
presents a "contact editor," implemented with Angular forms in the template-driven form style.
You can write Angular form components in template-driven or reactive style.
The following sample imports the FormsModule
from @angular/forms
because the ContactComponent
is written in template-driven style. Modules with components written in the reactive style import the ReactiveFormsModule
.
The ContactComponent
selector matches an element named <app-contact>
. Add an element with that name to the AppComponent
template, just below the <app-title>
:
template: ` <app-title [subtitle]="subtitle"></app-title> <app-contact></app-contact> `
Form components are often complex. The ContactComponent
has its own ContactService
and custom pipe (called Awesome
), and an alternative version of the HighlightDirective
.
To make it manageable, place all contact-related material in an src/app/contact
folder and break the component into three constituent HTML, TypeScript, and css files:
<h2>Contact of {{userName}}</h2> <div *ngIf="msg" class="msg">{{msg}}</div> <form *ngIf="contacts" (ngSubmit)="onSubmit()" #contactForm="ngForm"> <h3 highlight>{{ contact.name | awesome }}</h3> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" required [(ngModel)]="contact.name" name="name" #name="ngModel" > <div [hidden]="name.valid" class="alert alert-danger"> Name is required </div> </div> <br> <button type="submit" class="btn btn-default" [disabled]="!contactForm.form.valid">Save</button> <button type="button" class="btn" (click)="next()" [disabled]="!contactForm.form.valid">Next Contact</button> <button type="button" class="btn" (click)="newContact()">New Contact</button> </form>
import { Component, OnInit } from '@angular/core'; import { Contact, ContactService } from './contact.service'; import { UserService } from '../user.service'; @Component({ selector: 'app-contact', templateUrl: './contact.component.html', styleUrls: [ './contact.component.css' ] }) export class ContactComponent implements OnInit { contact: Contact; contacts: Contact[]; msg = 'Loading contacts ...'; userName = ''; constructor(private contactService: ContactService, userService: UserService) { this.userName = userService.userName; } ngOnInit() { this.contactService.getContacts().then(contacts => { this.msg = ''; this.contacts = contacts; this.contact = contacts[0]; }); } next() { let ix = 1 + this.contacts.indexOf(this.contact); if (ix >= this.contacts.length) { ix = 0; } this.contact = this.contacts[ix]; } onSubmit() { // POST-DEMO TODO: do something like save it this.displayMessage('Saved ' + this.contact.name); } newContact() { this.displayMessage('New contact'); this.contact = {id: 42, name: ''}; this.contacts.push(this.contact); } /** Display a message briefly, then remove it. */ displayMessage(msg: string) { this.msg = msg; setTimeout(() => this.msg = '', 1500); } }
.ng-valid[required] { border-left: 5px solid #42A948; /* green */ } .ng-invalid { border-left: 5px solid #a94442; /* red */ } .alert { padding: 15px; margin: 8px 0; border: 1px solid transparent; border-radius: 4px; } .alert-danger { color: #a94442; background-color: #f2dede; border-color: #ebccd1; } .msg { color: blue; background-color: whitesmoke; border: 1px solid transparent; border-radius: 4px; margin-bottom: 20px; }
import { Injectable } from '@angular/core'; export class Contact { constructor(public id: number, public name: string) { } } const CONTACTS: Contact[] = [ new Contact(21, 'Sam Spade'), new Contact(22, 'Nick Danger'), new Contact(23, 'Nancy Drew') ]; const FETCH_LATENCY = 500; @Injectable() export class ContactService { getContacts() { return new Promise<Contact[]>(resolve => { setTimeout(() => { resolve(CONTACTS); }, FETCH_LATENCY); }); } getContact(id: number | string) { return this.getContacts() .then(heroes => heroes.find(hero => hero.id === +id)); } }
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'awesome' }) /** Precede the input string with the word "Awesome " */ export class AwesomePipe implements PipeTransform { transform(phrase: string) { return phrase ? 'Awesome ' + phrase : ''; } }
import { Directive, ElementRef } from '@angular/core'; @Directive({ selector: '[highlight], input' }) /** Highlight the attached element or an InputElement in blue */ export class HighlightDirective { constructor(el: ElementRef) { el.nativeElement.style.backgroundColor = 'powderblue'; console.log( `* Contact highlight called for ${el.nativeElement.tagName}`); } }
In the middle of the component template, notice the two-way data binding [(ngModel)]
. ngModel
is the selector for the NgModel
directive.
Although NgModel
is an Angular directive, the Angular compiler won't recognize it for the following reasons:
AppModule
doesn't declare NgModel
.NgModel
wasn't imported via BrowserModule
.Even if Angular somehow recognized ngModel
, ContactComponent
wouldn't behave like an Angular form because form features such as validation aren't yet available.
Add the FormsModule
to the AppModule
metadata's imports
list.
imports: [ BrowserModule, FormsModule ],
Now [(ngModel)]
binding will work and the user input will be validated by Angular forms, once you declare the new component, pipe, and directive.
Do not add NgModel
—or the FORMS_DIRECTIVES
—to the AppModule
metadata's declarations. These directives belong to the FormsModule
.
Components, directives, and pipes belong to one module only.
Never re-declare classes that belong to another module.
The application won't compile until you declare the contact component, directive, and pipe. Update the declarations
in the AppModule
accordingly:
declarations: [ AppComponent, HighlightDirective, TitleComponent, AwesomePipe, ContactComponent, ContactHighlightDirective ],
There are two directives with the same name, both called HighlightDirective
.
To work around this, create an alias for the contact version using the as
JavaScript import keyword.
import { HighlightDirective as ContactHighlightDirective } from './contact/highlight.directive';
This solves the immediate issue of referencing both directive types in the same file but leaves another issue unresolved. You'll learn more about that issue later in this page, in Resolve directive conflicts.
The ContactComponent
displays contacts retrieved by the ContactService
, which Angular injects into its constructor.
You have to provide that service somewhere. The ContactComponent
could provide it, but then the service would be scoped to this component only. You want to share this service with other contact-related components that you'll surely add later.
In this app, add ContactService
to the AppModule
metadata's providers
list:
providers: [ ContactService, UserService ],
Now you can inject ContactService
(like UserService
) into any component in the application.
The ContactService
provider is application-scoped because Angular registers a module's providers
with the application's root injector.
Architecturally, the ContactService
belongs to the Contact business domain. Classes in other domains don't need the ContactService
and shouldn't inject it.
You might expect Angular to offer a module-scoping mechanism to enforce this design. It doesn't. NgModule instances, unlike components, don't have their own injectors so they can't have their own provider scopes.
This omission is intentional. NgModules are designed primarily to extend an application, to enrich the entire app with the module's capabilities.
In practice, service scoping is rarely an issue. Non-contact components can't accidentally inject the ContactService
. To inject ContactService
, you must first import its type. Only Contact components should import the ContactService
type.
Read more in the How do I restrict service scope to a module? section of the NgModule FAQs page.
Everything is in place to run the application with its contact editor.
The app file structure looks like this:
Try the example:
An issue arose earlier when you declared the contact's HighlightDirective
because you already had a HighlightDirective
class at the application level.
The selectors of the two directives both highlight the attached element with a different color.
import { Directive, ElementRef } from '@angular/core'; @Directive({ selector: '[highlight]' }) /** Highlight the attached element in gold */ export class HighlightDirective { constructor(el: ElementRef) { el.nativeElement.style.backgroundColor = 'gold'; console.log( `* AppRoot highlight called for ${el.nativeElement.tagName}`); } }
import { Directive, ElementRef } from '@angular/core'; @Directive({ selector: '[highlight], input' }) /** Highlight the attached element or an InputElement in blue */ export class HighlightDirective { constructor(el: ElementRef) { el.nativeElement.style.backgroundColor = 'powderblue'; console.log( `* Contact highlight called for ${el.nativeElement.tagName}`); } }
Both directives are declared in this module so both directives are active.
When the two directives compete to color the same element, the directive that's declared later wins because its DOM changes overwrite the first. In this case, the contact's HighlightDirective
makes the application title text blue when it should stay gold.
The issue is that two different classes are trying to do the same thing.
It's OK to import the same directive class multiple times. Angular removes duplicate classes and only registers one of them.
But from Angular's perspective, two different classes, defined in different files, that have the same name are not duplicates. Angular keeps both directives and they take turns modifying the same HTML element.
At least the app still compiles. If you define two different component classes with the same selector specifying the same element tag, the compiler reports an error. It can't insert two components in the same DOM location.
To eliminate component and directive conflicts, create feature modules that insulate the declarations in one module from the declarations in another.
This application isn't big yet, but it's already experiencing structural issues.
AppModule
grows larger with each new application class.HighlightDirective
in the contact re-colors the work done by the HighlightDirective
declared in AppModule
. Also, it colors the application title text when it should color only the ContactComponent
.You can resolve these issues with feature modules.
A feature module is a class adorned by the @NgModule
decorator and its metadata, just like a root module. Feature module metadata have the same properties as the metadata for a root module.
The root module and the feature module share the same execution context. They share the same dependency injector, which means the services in one module are available to all.
The modules have the following significant technical differences:
Otherwise, a feature module is distinguished primarily by its intent.
A feature module delivers a cohesive set of functionality focused on an application business domain, user workflow, facility (forms, http, routing), or collection of related utilities.
While you can do everything within the root module, feature modules help you partition the app into areas of specific interest and purpose.
A feature module collaborates with the root module and with other modules through the services it provides and the components, directives, and pipes that it shares.
In the next section, you'll carve the contact functionality out of the root module and into a dedicated feature module.
It's easy to refactor the contact material into a contact feature module.
ContactModule
in the src/app/contact
folder.AppModule
to ContactModule
.BrowserModule
with CommonModule
.ContactModule
into the AppModule
.AppModule
is the only existing class that changes. But you do add one new file.
Here's the new ContactModule
:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { AwesomePipe } from './awesome.pipe'; import { ContactComponent } from './contact.component'; import { ContactService } from './contact.service'; import { HighlightDirective } from './highlight.directive'; @NgModule({ imports: [ CommonModule, FormsModule ], declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], exports: [ ContactComponent ], providers: [ ContactService ] }) export class ContactModule { }
You copy from AppModule
the contact-related import statements and @NgModule
properties that concern the contact, and paste them into ContactModule
.
You import the FormsModule
because the contact component needs it.
Modules don't inherit access to the components, directives, or pipes that are declared in other modules. What AppModule
imports is irrelevant to ContactModule
and vice versa. Before ContactComponent
can bind with [(ngModel)]
, its ContactModule
must import FormsModule
.
You also replaced BrowserModule
by CommonModule
, for reasons explained in the Should I import BrowserModule or CommonModule? section of the NgModule FAQs page.
You declare the contact component, directive, and pipe in the module declarations
.
You export the ContactComponent
so other modules that import the ContactModule
can include it in their component templates.
All other declared contact classes are private by default. The AwesomePipe
and HighlightDirective
are hidden from the rest of the application. The HighlightDirective
can no longer color the AppComponent
title text.
Return to the AppModule
and remove everything specific to the contact feature set.
FormsModule
from the imports
list (AppComponent
doesn't need it).Leave only the classes required at the application root level.
Then import the ContactModule
so the app can continue to display the exported ContactComponent
.
Here's the refactored version of the AppModule
along with the previous version.
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component'; import { HighlightDirective } from './highlight.directive'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; /* Contact Imports */ import { ContactModule } from './contact/contact.module'; @NgModule({ imports: [ BrowserModule, ContactModule ], declarations: [ AppComponent, HighlightDirective, TitleComponent ], providers: [ UserService ], bootstrap: [ AppComponent ], }) export class AppModule { }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component'; import { HighlightDirective } from './highlight.directive'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; /* Contact Imports */ import { ContactComponent } from './contact/contact.component'; import { ContactService } from './contact/contact.service'; import { AwesomePipe } from './contact/awesome.pipe'; import { HighlightDirective as ContactHighlightDirective } from './contact/highlight.directive'; import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, HighlightDirective, TitleComponent, AwesomePipe, ContactComponent, ContactHighlightDirective ], providers: [ ContactService, UserService ], bootstrap: [ AppComponent ] }) export class AppModule { }
There's a lot to like in the revised AppModule
.
FormsModule
import.ContactService
provider.HighlightDirective
conflict.Try this ContactModule
version of the sample.
Try the live example.
The Heroic Staffing Agency sample app has evolved. It has two more modules, one for managing the heroes on staff and another for matching crises to the heroes. Both modules are in the early stages of development. Their specifics aren't important to the story and this page doesn't discuss every line of code.
Examine and download the complete source for this version from the live example.
Some facets of the current application merit discussion are as follows:
ContactComponent
is the default destination when the app starts.ContactModule
continues to be "eagerly" loaded when the application starts.HeroModule
and the CrisisModule
are lazy loaded. The new AppComponent
template has a title, three links, and a <router-outlet>
.
template: ` <app-title [subtitle]="subtitle"></app-title> <nav> <a routerLink="contact" routerLinkActive="active">Contact</a> <a routerLink="crisis" routerLinkActive="active">Crisis Center</a> <a routerLink="heroes" routerLinkActive="active">Heroes</a> </nav> <router-outlet></router-outlet> `
The <app-contact>
element is gone; you're routing to the Contact page now.
The AppModule
has changed modestly:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component.3'; import { HighlightDirective } from './highlight.directive'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; /* Feature Modules */ import { ContactModule } from './contact/contact.module.3'; /* Routing Module */ import { AppRoutingModule } from './app-routing.module.3'; @NgModule({ imports: [ BrowserModule, ContactModule, AppRoutingModule ], providers: [ UserService ], declarations: [ AppComponent, HighlightDirective, TitleComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
Some file names bear a .3
extension that indicates a difference with prior or future versions. The significant differences will be explained in due course.
The module still imports ContactModule
so that its routes and components are mounted when the app starts.
The module does not import HeroModule
or CrisisModule
. They'll be fetched and mounted asynchronously when the user navigates to one of their routes.
The significant change from version 2 is the addition of the AppRoutingModule to the module imports
. The AppRoutingModule
is a routing module that handles the app's routing concerns.
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; export const routes: Routes = [ { path: '', redirectTo: 'contact', pathMatch: 'full'}, { path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' }, { path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}
The router is the subject of the Routing & Navigation page, so this section skips many of the details and concentrates on the intersection of NgModules and routing.
The app-routing.module.ts
file defines three routes.
The first route redirects the empty URL (such as http://host.com/
) to another route whose path is contact
(such as http://host.com/contact
).
The contact
route isn't defined here. It's defined in the Contact feature's own routing module, contact-routing.module.ts
. It's standard practice for feature modules with routing components to define their own routes. You'll get to that file in a moment.
The remaining two routes use lazy loading syntax to tell the router where to find the modules:
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' }, { path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
A lazy-loaded module location is a string, not a type. In this app, the string identifies both the module file and the module class, the latter separated from the former by a #
.
The forRoot
static class method of the RouterModule
with the provided configuration and added to the imports
array provides the routing concerns for the module.
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}
The returned AppRoutingModule
class is a Routing Module
containing both the RouterModule
directives and the dependency-injection providers that produce a configured Router
.
This AppRoutingModule
is intended for the app root module only.
Never call RouterModule.forRoot
in a feature-routing module.
Back in the root AppModule
, add the AppRoutingModule
to its imports
list, and the app is ready to navigate.
imports: [ BrowserModule, ContactModule, AppRoutingModule ],
The src/app/contact
folder holds a new file, contact-routing.module.ts
. It defines the contact
route mentioned earlier and provides a ContactRoutingModule
as follows:
@NgModule({ imports: [RouterModule.forChild([ { path: 'contact', component: ContactComponent } ])], exports: [RouterModule] }) export class ContactRoutingModule {}
This time you pass the route list to the forChild
method of the RouterModule
. The route list is only responsible for providing additional routes and is intended for feature modules.
Always call RouterModule.forChild
in a feature-routing module.
forRoot and forChild are conventional names for methods that deliver different import
values to root and feature modules. Angular doesn't recognize them but Angular developers do.
Follow this convention if you write a similar module that has both shared declarables and services.
ContactModule
has changed in two small but important ways.
@NgModule({ imports: [ CommonModule, FormsModule, ContactRoutingModule ], declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], providers: [ ContactService ] }) export class ContactModule { }
@NgModule({ imports: [ CommonModule, FormsModule ], declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], exports: [ ContactComponent ], providers: [ ContactService ] }) export class ContactModule { }
ContactRoutingModule
object from contact-routing.module.ts
.ContactComponent
.Now that you navigate to ContactComponent
with the router, there's no reason to make it public. Also, ContactComponent
doesn't need a selector. No template will ever again reference this ContactComponent
. It's gone from the AppComponent template.
The lazy-loaded HeroModule
and CrisisModule
follow the same principles as any feature module. They don't look different from the eagerly loaded ContactModule
.
The HeroModule
is a bit more complex than the CrisisModule
, which makes it a more interesting and useful example. Its file structure is as follows:
This is the child routing scenario familiar to readers of the Child routing component section of the Routing & Navigation page. The HeroComponent
is the feature's top component and routing host. Its template has a <router-outlet>
that displays either a list of heroes (HeroList
) or an editor of a selected hero (HeroDetail
). Both components delegate to the HeroService
to fetch and save data.
Yet another HighlightDirective
colors elements in yet a different shade. In the next section, Shared modules, you'll resolve the repetition and inconsistencies.
The HeroModule
is a feature module like any other.
@NgModule({ imports: [ CommonModule, FormsModule, HeroRoutingModule ], declarations: [ HeroComponent, HeroDetailComponent, HeroListComponent, HighlightDirective ] }) export class HeroModule { }
It imports the FormsModule
because the HeroDetailComponent
template binds with [(ngModel)]
. It imports the HeroRoutingModule
from hero-routing.module.ts
just as ContactModule
and CrisisModule
do.
The CrisisModule
is much the same.
Try the live example.
The app is shaping up. But it carries three different versions of the HighlightDirective
. And the many files cluttering the app folder level could be better organized.
Add a SharedModule
to hold the common components, directives, and pipes and share them with the modules that need them.
src/app/shared
folder.AwesomePipe
and HighlightDirective
from src/app/contact
to src/app/shared
.HighlightDirective
classes from src/app/
and src/app/hero
.SharedModule
class to own the shared material.SharedModule
.Here is the SharedModule
:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { AwesomePipe } from './awesome.pipe'; import { HighlightDirective } from './highlight.directive'; @NgModule({ imports: [ CommonModule ], declarations: [ AwesomePipe, HighlightDirective ], exports: [ AwesomePipe, HighlightDirective, CommonModule, FormsModule ] }) export class SharedModule { }
Note the following:
CommonModule
because its component needs common directives.CommonModule
and FormsModule
If you review the application, you may notice that many components requiring SharedModule
directives also use NgIf
and NgFor
from CommonModule
and bind to component properties with [(ngModel)]
, a directive in the FormsModule
. Modules that declare these components would have to import CommonModule
, FormsModule
, and SharedModule
.
You can reduce the repetition by having SharedModule
re-export CommonModule
and FormsModule
so that importers of SharedModule
get CommonModule
and FormsModule
for free.
As it happens, the components declared by SharedModule
itself don't bind with [(ngModel)]
. Technically, there is no need for SharedModule
to import FormsModule
.
SharedModule
can still export FormsModule
without listing it among its imports
.
SharedModule
exists to make commonly used components, directives, and pipes available for use in the templates of components in many other modules.
The TitleComponent
is used only once by the AppComponent
. There's no point in sharing it.
While many components share the same service instances, they rely on Angular dependency injection to do this kind of sharing, not the module system.
Several components of the sample inject the UserService
. There should be only one instance of the UserService
in the entire application and only one provider of it.
UserService
is an application-wide singleton. You don't want each module to have its own separate instance. Yet there is a real danger of that happening if the SharedModule
provides the UserService
.
Do not specify app-wide singleton providers
in a shared module. A lazy-loaded module that imports that shared module makes its own copy of the service.
At the moment, the root folder is cluttered with the UserService
and TitleComponent
that only appear in the root AppComponent
. You didn't include them in the SharedModule
for reasons just explained.
Instead, gather them in a single CoreModule
that you import once when the app starts and never import anywhere else.
Perform the following steps:
src/app/core
folder.UserService
and TitleComponent
from src/app/
to src/app/core
.CoreModule
class to own the core material.AppRoot
module to import CoreModule
.Most of this work is familiar. The interesting part is the CoreModule
.
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; @NgModule({ imports: [ CommonModule ], declarations: [ TitleComponent ], exports: [ TitleComponent ], providers: [ UserService ] }) export class CoreModule { }
You're importing some extra symbols from the Angular core library that you're not using yet. They'll become relevant later in this page.
The @NgModule
metadata should be familiar. You declare the TitleComponent
because this module owns it and you export it because AppComponent
(which is in AppModule
) displays the title in its template. TitleComponent
needs the Angular NgIf
directive that you import from CommonModule
.
CoreModule
provides the UserService
. Angular registers that provider with the app root injector, making a singleton instance of the UserService
available to any component that needs it, whether that component is eagerly or lazily loaded.
This scenario is clearly contrived. The app is too small to worry about a single service file and a tiny, one-time component.
A TitleComponent
sitting in the root folder isn't bothering anyone. The root AppModule
can register the UserService
itself, as it does currently, even if you decide to relocate the UserService
file to the src/app/core
folder.
Real-world apps have more to worry about. They can have several single-use components (such as spinners, message toasts, and modal dialogs) that appear only in the AppComponent
template. You don't import them elsewhere so they're not shared in that sense. Yet they're too big and messy to leave loose in the root folder.
Apps often have many singleton services like this sample's UserService
. Each must be registered exactly once, in the app root injector, when the application starts.
While many components inject such services in their constructors—and therefore require JavaScript import
statements to import their symbols—no other component or module should define or re-create the services themselves. Their providers aren't shared.
We recommend collecting such single-use classes and hiding their details inside a CoreModule
. A simplified root AppModule
imports CoreModule
in its capacity as orchestrator of the application as a whole.
Having refactored to a CoreModule
and a SharedModule
, it's time to clean up the other modules.
Here is the updated AppModule
paired with version 3 for comparison:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component'; /* Feature Modules */ import { ContactModule } from './contact/contact.module'; import { CoreModule } from './core/core.module'; /* Routing Module */ import { AppRoutingModule } from './app-routing.module'; @NgModule({ imports: [ BrowserModule, ContactModule, CoreModule, AppRoutingModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; /* App Root */ import { AppComponent } from './app.component.3'; import { HighlightDirective } from './highlight.directive'; import { TitleComponent } from './title.component'; import { UserService } from './user.service'; /* Feature Modules */ import { ContactModule } from './contact/contact.module.3'; /* Routing Module */ import { AppRoutingModule } from './app-routing.module.3'; @NgModule({ imports: [ BrowserModule, ContactModule, AppRoutingModule ], providers: [ UserService ], declarations: [ AppComponent, HighlightDirective, TitleComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
AppModule
now has the following qualities:
src/app/root
classes have moved to other modules.Here is the new ContactModule
paired with the prior version:
import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { ContactComponent } from './contact.component'; import { ContactService } from './contact.service'; import { ContactRoutingModule } from './contact-routing.module'; @NgModule({ imports: [ SharedModule, ContactRoutingModule ], declarations: [ ContactComponent ], providers: [ ContactService ] }) export class ContactModule { }
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { AwesomePipe } from './awesome.pipe'; import { ContactComponent } from './contact.component.3'; import { ContactService } from './contact.service'; import { HighlightDirective } from './highlight.directive'; import { ContactRoutingModule } from './contact-routing.module.3'; @NgModule({ imports: [ CommonModule, FormsModule, ContactRoutingModule ], declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], providers: [ ContactService ] }) export class ContactModule { }
Notice the following:
AwesomePipe
and HighlightDirective
are gone.SharedModule
instead of CommonModule
and FormsModule
.A module that adds providers to the application can offer a facility for configuring those providers as well.
By convention, the forRoot
static method both provides and configures services at the same time. It takes a service configuration object and returns a ModuleWithProviders, which is a simple object with the following properties:
ngModule
: the CoreModule
classproviders
: the configured providersThe root AppModule
imports the CoreModule
and adds the providers
to the AppModule
providers.
More precisely, Angular accumulates all imported providers before appending the items listed in @NgModule.providers
. This sequence ensures that whatever you add explicitly to the AppModule
providers takes precedence over the providers of imported modules.
Add a CoreModule.forRoot
method that configures the core UserService
.
You've extended the core UserService
with an optional, injected UserServiceConfig
. If a UserServiceConfig
exists, the UserService
sets the user name from that config.
constructor(@Optional() config: UserServiceConfig) { if (config) { this._userName = config.userName; } }
Here's CoreModule.forRoot
that takes a UserServiceConfig
object:
static forRoot(config: UserServiceConfig): ModuleWithProviders { return { ngModule: CoreModule, providers: [ {provide: UserServiceConfig, useValue: config } ] }; }
Lastly, call it within the imports
list of the AppModule
.
imports: [ BrowserModule, ContactModule, CoreModule.forRoot({userName: 'Miss Marple'}), AppRoutingModule ],
The app displays "Miss Marple" as the user instead of the default "Sherlock Holmes".
Call forRoot
only in the root application module, AppModule
. Calling it in any other module, particularly in a lazy-loaded module, is contrary to the intent and can produce a runtime error.
Remember to import the result; don't add it to any other @NgModule
list.
Only the root AppModule
should import the CoreModule
. Bad things happen if a lazy-loaded module imports it.
You could hope that no developer makes that mistake. Or you can guard against it and fail fast by adding the following CoreModule
constructor.
constructor (@Optional() @SkipSelf() parentModule: CoreModule) { if (parentModule) { throw new Error( 'CoreModule is already loaded. Import it in the AppModule only'); } }
The constructor tells Angular to inject the CoreModule
into itself. That seems dangerously circular.
The injection would be circular if Angular looked for CoreModule
in the current injector. The @SkipSelf
decorator means "look for CoreModule
in an ancestor injector, above me in the injector hierarchy."
If the constructor executes as intended in the AppModule
, there is no ancestor injector that could provide an instance of CoreModule
. The injector should give up.
By default, the injector throws an error when it can't find a requested provider. The @Optional
decorator means not finding the service is OK. The injector returns null
, the parentModule
parameter is null, and the constructor concludes uneventfully.
It's a different story if you improperly import CoreModule
into a lazy-loaded module such as HeroModule
(try it).
Angular creates a lazy-loaded module with its own injector, a child of the root injector. @SkipSelf
causes Angular to look for a CoreModule
in the parent injector, which this time is the root injector. Of course it finds the instance imported by the root AppModule
. Now parentModule
exists and the constructor throws the error.
You made it! You can examine and download the complete source for this final version from the live example.
Now that you understand NgModules, you may be interested in the companion NgModule FAQs page with its ready answers to specific design and implementation questions.
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v2.angular.io/docs/ts/latest/guide/ngmodule.html