1216 lines
45 KiB
Markdown
1216 lines
45 KiB
Markdown
|
@title
|
||
|
Angular Modules (NgModule)
|
||
|
|
||
|
@intro
|
||
|
Define application modules with @NgModule
|
||
|
|
||
|
@description
|
||
|
**Angular Modules** help organize an application into cohesive blocks of functionality.
|
||
|
|
||
|
An Angular Module 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.
|
||
|
It may add service providers to the application dependency injectors.
|
||
|
And there are many more options covered here.
|
||
|
|
||
|
[The Root Module](appmodule.html) guide introduced Angular Modules and the essentials
|
||
|
of creating and maintaining a single _root_ `AppModule` for the entire application.
|
||
|
Read that first.
|
||
|
|
||
|
This page goes into Angular modules in much greater depth.
|
||
|
|
||
|
## Table of Contents
|
||
|
* [Angular modularity](#angular-modularity "Add structure to the app with NgModule")
|
||
|
* [The application root module](#root-module "The startup module that every app requires")
|
||
|
* [Bootstrap](#bootstrap "Launch the app in a browser with the root module as the entry point") the root module
|
||
|
* [Declarations](#declarations "Declare the components, directives, and pipes that belong to a module")
|
||
|
* [Providers](#providers "Extend the app with additional services")
|
||
|
* [Imports](#imports "Import components, directives, and pipes for use in component templates")
|
||
|
* [Resolve conflicts](#resolve-conflicts "When two directives have the same selector ...")
|
||
|
* [Feature modules](#feature-modules "Partition the app into feature modules")
|
||
|
* [Lazy loaded modules](#lazy-load "Load modules asynchronously") with the Router
|
||
|
* [Shared modules](#shared-module "Create modules for commonly used components, directives, and pipes")
|
||
|
* [The Core module](#core-module "Create a core module with app-wide singleton services and single-use components")
|
||
|
* [Configure core services with _forRoot_](#core-for-root "Configure providers during module import")
|
||
|
* [Prevent reimport of the _CoreModule_](#prevent-reimport "because bad things happen if a lazy loaded module imports Core")
|
||
|
* [NgModule metadata properties](#ngmodule-properties "A technical summary of the @NgModule metadata properties")
|
||
|
|
||
|
### Live examples
|
||
|
This page explains Angular Modules 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 that sample:
|
||
|
|
||
|
* <live-example plnkr="minimal.0">A minimal NgModule app</live-example>
|
||
|
* <live-example plnkr="contact.1b">The first contact module</live-example>
|
||
|
* <live-example plnkr="contact.2">The revised contact module</live-example>
|
||
|
* <live-example plnkr="pre-shared.3">Just before adding _SharedModule_</live-example>
|
||
|
* <live-example>The final version</live-example>
|
||
|
|
||
|
### Frequently Asked Questions (FAQs)
|
||
|
|
||
|
This page covers Angular Module concepts in a tutorial fashion.
|
||
|
|
||
|
The companion [Angular Module FAQs](../cookbook/ngmodule-faq.html "Angular Module FAQs") cookbook
|
||
|
offers ready answers to specific design and implementation questions.
|
||
|
Read this page first before hopping over to those FAQs.
|
||
|
|
||
|
<div class='l-hr'>
|
||
|
|
||
|
</div>
|
||
|
|
||
|
|
||
|
|
||
|
{@a angular-modularity}
|
||
|
|
||
|
## Angular Modularity
|
||
|
|
||
|
Modules are a great way to organize the application and extend it with capabilities from external libraries.
|
||
|
|
||
|
Many Angular libraries are modules (e.g, `FormsModule`, `HttpModule`, `RouterModule`).
|
||
|
Many third party libraries are available as Angular modules (e.g.,
|
||
|
<a href="https://material.angular.io/" target="_blank">Material Design</a>,
|
||
|
<a href="http://ionicframework.com/" target="_blank">Ionic</a>,
|
||
|
<a href="https://github.com/angular/angularfire2" target="_blank">AngularFire2</a>).
|
||
|
|
||
|
Angular modules 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.
|
||
|
They 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 Angular module is a class decorated with `@NgModule` metadata. The metadata:
|
||
|
|
||
|
* declare which components, directives and pipes _belong_ to the module.
|
||
|
* make some of those classes public so that other component templates can use them.
|
||
|
* import other modules with the components, directives and pipes needed by the components in _this_ module.
|
||
|
* provide services at the application level that any application component can use.
|
||
|
|
||
|
Every Angular app has at least one module class, the _root module_.
|
||
|
We bootstrap that module to launch the application.
|
||
|
|
||
|
The _root module_ is all we need in a simple application with a few components.
|
||
|
As the app grows, we refactor the _root module_ into **feature modules**
|
||
|
that represent collections of related functionality.
|
||
|
We then import these modules into the _root module_.
|
||
|
|
||
|
We'll see how later in the page. Let's start with the _root module_.
|
||
|
|
||
|
|
||
|
{@a root-module}
|
||
|
|
||
|
## _AppModule_ - the application root module
|
||
|
|
||
|
Every Angular app has a **root module** class.
|
||
|
By convention it's a class called `AppModule` in a file named `app.module.ts`.
|
||
|
|
||
|
The `AppModule` from the [_QuickStart seed_](setup.html) is about as minimal as it gets:
|
||
|
|
||
|
{@example 'setup/ts/src/app/app.module.ts'}
|
||
|
|
||
|
The `@NgModule` decorator defines the metadata for the module.
|
||
|
We'll take an intuitive approach to understanding the metadata and fill in details as we go.
|
||
|
|
||
|
This metadata imports a single helper module, `BrowserModule`, the module 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 modules component templates.
|
||
|
|
||
|
The `declarations` list identifies the application's only component,
|
||
|
the _root component_, the top of this app's rather bare component tree.
|
||
|
|
||
|
The example `AppComponent` simply displays a data-bound title:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.component.0.ts'}
|
||
|
|
||
|
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`
|
||
|
|
||
|
|
||
|
{@a bootstrap}
|
||
|
|
||
|
## Bootstrapping in _main.ts_
|
||
|
We launch the application by bootstrapping the `AppModule` in the `main.ts` file.
|
||
|
|
||
|
Angular offers a variety of bootstrapping options, targeting multiple platforms.
|
||
|
In this page we consider two options, both targeting the browser.
|
||
|
|
||
|
### Dynamic bootstrapping with the Just-in-time (JIT) compiler
|
||
|
In the first, _dynamic_ option, the [Angular compiler](../cookbook/ngmodule-faq.html#q-angular-compiler "About the Angular Compiler")
|
||
|
compiles the application in the browser and then launches the app.
|
||
|
|
||
|
|
||
|
{@example 'ngmodule/ts/src/main.ts'}
|
||
|
|
||
|
The samples in this page demonstrate the dynamic bootstrapping approach.
|
||
|
|
||
|
<live-example embedded plnkr="minimal.0" img="devguide/ngmodule/minimal-plunker.png">Try the live example.</live-example>
|
||
|
|
||
|
|
||
|
### Static bootstrapping with the Ahead-Of-time (AOT) compiler
|
||
|
|
||
|
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.
|
||
|
|
||
|
|
||
|
{@example 'ngmodule/ts/src/main-static.ts'}
|
||
|
|
||
|
Because the entire application was pre-compiled,
|
||
|
we don't ship the _Angular Compiler_ to the browser and we don't compile in the browser.
|
||
|
|
||
|
The application code downloaded to the browser is much smaller than the dynamic equivalent
|
||
|
and it is 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 we're importing 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 we'll look at `main.ts`.
|
||
|
|
||
|
<div class='l-hr'>
|
||
|
|
||
|
</div>
|
||
|
|
||
|
|
||
|
|
||
|
{@a declarations}
|
||
|
|
||
|
## Declare directives and components
|
||
|
The app evolves.
|
||
|
The first addition is a `HighlightDirective`, an [attribute directive](attribute-directives.html)
|
||
|
that sets the background color of the attached element.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/highlight.directive.ts'}
|
||
|
|
||
|
We update the `AppComponent` template to attach the directive to the title:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.component.1.ts' region='template'}
|
||
|
|
||
|
If we ran the app now, Angular would not recognize the `highlight` attribute and would ignore it.
|
||
|
We must declare the directive in `AppModule`.
|
||
|
|
||
|
Import the `HighlightDirective` class and add it to the module's `declarations` like this:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='directive'}
|
||
|
|
||
|
### Add a component
|
||
|
|
||
|
We decide to refactor the title into its own `TitleComponent`.
|
||
|
The component's template binds to the component's `title` and `subtitle` properties like this:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/title.component.html' region='v1'}
|
||
|
|
||
|
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/title.component.ts' region='v1'}
|
||
|
|
||
|
We rewrite the `AppComponent` to display the new `TitleComponent` in the `<app-title>` element,
|
||
|
using an input binding to set the `subtitle`.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.component.1.ts'}
|
||
|
|
||
|
Angular won't recognize the `<app-title>` tag until we declare it in `AppModule`.
|
||
|
Import the `TitleComponent` class and add it to the module's `declarations`:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='component'}
|
||
|
|
||
|
|
||
|
|
||
|
{@a providers}
|
||
|
|
||
|
## Service Providers
|
||
|
|
||
|
Modules are a great way to provide services for all of the module's components.
|
||
|
|
||
|
The [Dependency Injection](dependency-injection.html) page describes
|
||
|
the Angular hierarchical dependency injection system and how to configure that system
|
||
|
with [providers](dependency-injection.html#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`.
|
||
|
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/user.service.ts'}
|
||
|
|
||
|
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.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/title.component.html'}
|
||
|
|
||
|
Update the `TitleComponent` class with a constructor that injects the `UserService`
|
||
|
and sets the component's `user` property from the service.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/title.component.ts'}
|
||
|
|
||
|
We've _defined_ and _used_ the service. Now we _provide_ it for all components to use by
|
||
|
adding it to a `providers` property in the `AppModule` metadata:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='providers'}
|
||
|
|
||
|
|
||
|
|
||
|
{@a imports}
|
||
|
|
||
|
## Import supporting modules
|
||
|
|
||
|
The app shouldn't welcome a user if there is no user.
|
||
|
|
||
|
Notice in the revised `TitleComponent` that an `*ngIf` directive guards the message.
|
||
|
There is no message if there is no user.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/title.component.html' region='ngIf'}
|
||
|
|
||
|
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 we imported it earlier.
|
||
|
The initial version of `AppModule` imports `BrowserModule`.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.0.ts' region='imports'}
|
||
|
|
||
|
Importing `BrowserModule` made all of its public components, directives and pipes visible
|
||
|
to the component templates in `AppModule`. They are ready to use without further ado.
|
||
|
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_](../cookbook/ngmodule-faq.html#q-re-export) it.
|
||
|
The net effect is that an importer of `BrowserModule` gets `CommonModule` directives automatically.Many familiar Angular directives do not belong to`CommonModule`.
|
||
|
For example, `NgModel` and `RouterLink` belong to Angular's `FormsModule` and `RouterModule` respectively.
|
||
|
We must _import_ those modules before we can use their directives.
|
||
|
|
||
|
To illustrate this point, we extend the sample app with `ContactComponent`,
|
||
|
a form component that imports form support from the Angular `FormsModule`.
|
||
|
|
||
|
### Add the _ContactComponent_
|
||
|
|
||
|
[Angular Forms](forms.html) are a great way to manage user data entry.
|
||
|
|
||
|
The `ContactComponent` presents a "contact editor",
|
||
|
implemented with _Angular Forms_ in the [_template-driven form_](forms.html) style.
|
||
|
|
||
|
### Angular Form Styles
|
||
|
|
||
|
We write Angular form components in either the
|
||
|
[_template-driven form_](forms.html) style or
|
||
|
the [_reactive form_](../cookbook/dynamic-form.html) style.
|
||
|
|
||
|
This sample is about to import the `FormsModule` from `@angular/forms` because
|
||
|
the `ContactComponent` is written in the _template-driven_ style.
|
||
|
Modules with components written in the _reactive_ style,
|
||
|
should import the `ReactiveFormsModule` instead.
|
||
|
The `ContactComponent` selector matches an element named `<app-contact>`.
|
||
|
Add an element with that name to the `AppComponent` template just below the `<app-title>`:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.component.1b.ts' region='template'}
|
||
|
|
||
|
The `ContactComponent` has a lot going on.
|
||
|
Form components are often complex anyway and this one has its own `ContactService`,
|
||
|
its own [custom pipe](#pipes.html#custom-pipes) called `Awesome`,
|
||
|
and an alternative version of the `HighlightDirective`.
|
||
|
|
||
|
To make it manageable, we place all contact-related material in an `src/app/contact` folder
|
||
|
and break the component into three constituent HTML, TypeScript, and css files:
|
||
|
<md-tab-group>
|
||
|
|
||
|
<md-tab label="src/app/contact/contact.component.html">
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.component.html'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/contact/contact.component.ts">
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.component.3.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/contact/contact.component.css">
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.component.css'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/contact/contact.service.ts">
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.service.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/contact/awesome.pipe.ts">
|
||
|
{@example 'ngmodule/ts/src/app/contact/awesome.pipe.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/contact/highlight.directive.ts">
|
||
|
{@example 'ngmodule/ts/src/app/contact/highlight.directive.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
</md-tab-group>
|
||
|
|
||
|
Focus on the component template.
|
||
|
Notice the two-way data binding `[(ngModel)]` in the middle of the template.
|
||
|
`ngModel` is the selector for the `NgModel` directive.
|
||
|
|
||
|
Although `NgModel` is an Angular directive, the _Angular Compiler_ won't recognize it
|
||
|
because (a) `AppModule` doesn't declare it and (b) it wasn't imported via `BrowserModule`.
|
||
|
|
||
|
Less obviously, even if Angular somehow recognized `ngModel`,
|
||
|
this `ContactComponent` would not behave like an Angular form because
|
||
|
form features such as validation are not yet available.
|
||
|
|
||
|
### Import the FormsModule
|
||
|
|
||
|
Add the `FormsModule` to the `AppModule` metadata's `imports` list.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='imports'}
|
||
|
|
||
|
Now `[(ngModel)]` binding will work and the user input will be validated by Angular Forms,
|
||
|
once we declare our new component, pipe and directive.
|
||
|
|
||
|
|
||
|
~~~ {.alert.is-critical}
|
||
|
|
||
|
**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 — and _one module only_.
|
||
|
|
||
|
**Never re-declare classes that belong to another module.**
|
||
|
|
||
|
|
||
|
~~~
|
||
|
|
||
|
|
||
|
|
||
|
{@a declare-pipe}
|
||
|
### Declare the contact component, directive and pipe
|
||
|
|
||
|
The application fails to compile until we declare the contact component, directive and pipe.
|
||
|
Update the `declarations` in the `AppModule` accordingly:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.1.ts' region='declarations'}
|
||
|
|
||
|
|
||
|
|
||
|
{@a import-name-conflict}
|
||
|
|
||
|
There are two directives with the same name, both called `HighlightDirective`.
|
||
|
|
||
|
We work around it by creating an alias for the second, contact version using the `as` JavaScript import keyword:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.1b.ts' region='import-alias'}
|
||
|
|
||
|
This solves the immediate problem of referencing both directive _types_ in the same file but
|
||
|
leaves another problem unresolved as we discuss [below](#resolve-conflicts).
|
||
|
### Provide the _ContactService_
|
||
|
The `ContactComponent` displays contacts retrieved by the `ContactService`
|
||
|
which Angular injects into its constructor.
|
||
|
|
||
|
We have to provide that service somewhere.
|
||
|
The `ContactComponent` _could_ provide it.
|
||
|
But then it would be scoped to this component _only_.
|
||
|
We want to share this service with other contact-related components that we will surely add later.
|
||
|
|
||
|
In this app we chose to add `ContactService` to the `AppModule` metadata's `providers` list:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.1b.ts' region='providers'}
|
||
|
|
||
|
Now `ContactService` (like `UserService`) can be injected into any component in the application.
|
||
|
|
||
|
|
||
|
{@a application-scoped-providers}
|
||
|
|
||
|
### Application-scoped Providers
|
||
|
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.
|
||
|
|
||
|
We might expect Angular to offer a _module_-scoping mechanism to enforce this design.
|
||
|
It doesn't. Angular module instances, unlike components, do not have their own injectors
|
||
|
so they can't have their own provider scopes.
|
||
|
|
||
|
This omission is intentional.
|
||
|
Angular modules are designed primarily to extend an application,
|
||
|
to enrich the entire app with the module's capabilities.
|
||
|
|
||
|
Service scoping is rarely a problem in practice.
|
||
|
Non-contact components can't inject the `ContactService` by accident.
|
||
|
To inject `ContactService`, you must first import its _type_.
|
||
|
Only Contact components should import the `ContactService` _type_.
|
||
|
|
||
|
See the [FAQ that pursues this issue](../cookbook/ngmodule-faq.html#q-component-scoped-providers)
|
||
|
and its mitigations in greater detail.
|
||
|
### Run the app
|
||
|
Everything is now in place to run the application with its contact editor.
|
||
|
|
||
|
The app file structure looks like this:
|
||
|
<aio-filetree>
|
||
|
|
||
|
<aio-folder>
|
||
|
app
|
||
|
<aio-file>
|
||
|
app.component.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
app.module.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
highlight.directive.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
title.component.(html|ts)
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
user.service.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-folder>
|
||
|
contact
|
||
|
<aio-file>
|
||
|
awesome.pipe.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
contact.component.(css|html|ts)
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
contact.service.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
highlight.directive.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
</aio-folder>
|
||
|
|
||
|
|
||
|
</aio-folder>
|
||
|
|
||
|
|
||
|
</aio-filetree>
|
||
|
|
||
|
Try the example:
|
||
|
<live-example embedded plnkr="contact.1b" img="devguide/ngmodule/contact-1b-plunker.png"></live-example>
|
||
|
|
||
|
|
||
|
{@a resolve-conflicts}
|
||
|
|
||
|
## Resolve directive conflicts
|
||
|
|
||
|
We ran into trouble [above](#import-name-conflict) when we declared the contact's `HighlightDirective` because
|
||
|
we already had a `HighlightDirective` class at the application level.
|
||
|
|
||
|
That both directives have the same name smells of trouble.
|
||
|
|
||
|
A look at their selectors reveals that they both highlight the attached element with a different color.
|
||
|
<md-tab-group>
|
||
|
|
||
|
<md-tab label="src/app/highlight.directive.ts">
|
||
|
{@example 'ngmodule/ts/src/app/highlight.directive.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/contact/highlight.directive.ts">
|
||
|
{@example 'ngmodule/ts/src/app/contact/highlight.directive.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
</md-tab-group>
|
||
|
|
||
|
Will Angular use only one of them? No.
|
||
|
Both directives are declared in this module so _both directives are active_.
|
||
|
|
||
|
When the two directives compete to color the same element,
|
||
|
the directive declared later wins because its DOM changes overwrite the first.
|
||
|
In this case, the contact's `HighlightDirective` colors the application title text blue
|
||
|
when it should stay gold.
|
||
|
|
||
|
The real problem is that there are _two different classes_ 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 these are actually two different classes, defined in different files, that happen to have the same name.
|
||
|
|
||
|
They're not duplicates from Angular's perspective. Angular keeps both directives and
|
||
|
they take turns modifying the same HTML element.
|
||
|
At least the app still compiles.
|
||
|
If we 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.
|
||
|
|
||
|
What a mess!
|
||
|
|
||
|
We can eliminate component and directive conflicts by creating feature modules
|
||
|
that insulate the declarations in one module from the declarations in another.
|
||
|
|
||
|
|
||
|
{@a feature-modules}
|
||
|
|
||
|
## Feature Modules
|
||
|
|
||
|
This application isn't big yet. But it's already suffering structural problems.
|
||
|
|
||
|
* The root `AppModule` grows larger with each new application class and shows no signs of stopping.
|
||
|
|
||
|
* We have conflicting directives.
|
||
|
The `HighlightDirective` in contact is re-coloring the work done by the `HighlightDirective` declared in `AppModule`.
|
||
|
And it's coloring the application title text when it should only color the `ContactComponent`.
|
||
|
|
||
|
* The app lacks clear boundaries between contact functionality and other application features.
|
||
|
That lack of clarity makes it harder to assign development responsibilities to different teams.
|
||
|
|
||
|
We mitigate these problems with _feature modules_.
|
||
|
|
||
|
### _Feature Module_
|
||
|
|
||
|
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.
|
||
|
|
||
|
There are two significant technical differences:
|
||
|
|
||
|
1. We _boot_ the root module to _launch_ the app;
|
||
|
we _import_ a feature module to _extend_ the app.
|
||
|
|
||
|
2. A feature module can expose or hide its implementation from other modules.
|
||
|
|
||
|
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, a user workflow, a facility (forms, http, routing),
|
||
|
or a collection of related utilities.
|
||
|
|
||
|
While we can do everything within the root module,
|
||
|
feature modules help us 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 chooses to share.
|
||
|
|
||
|
In the next section, we carve the contact functionality out of the root module
|
||
|
and into a dedicated feature module.
|
||
|
|
||
|
<a id="contact-module-v1"></a>
|
||
|
### Make _Contact_ a feature module
|
||
|
|
||
|
It's easy to refactor the contact material into a contact feature module.
|
||
|
|
||
|
1. Create the `ContactModule` in the `src/app/contact` folder.
|
||
|
1. Move the contact material from `AppModule` to `ContactModule`.
|
||
|
1. Replace the imported `BrowserModule` with `CommonModule`.
|
||
|
1. Import the `ContactModule` into the `AppModule`.
|
||
|
|
||
|
`AppModule` is the only _existing_ class that changes. But we do add one new file.
|
||
|
|
||
|
### Add the _ContactModule_
|
||
|
|
||
|
Here's the new `ContactModule`
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.module.2.ts'}
|
||
|
|
||
|
We copy from `AppModule` the contact-related import statements and the `@NgModule` properties
|
||
|
that concern the contact and paste them in `ContactModule`.
|
||
|
|
||
|
We _import_ the `FormsModule` because the contact component needs it.
|
||
|
|
||
|
~~~ {.alert.is-important}
|
||
|
|
||
|
Modules do not 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`.
|
||
|
|
||
|
~~~
|
||
|
|
||
|
We also replaced `BrowserModule` by `CommonModule` for reasons explained in
|
||
|
[an FAQ](../cookbook/ngmodule-faq.html#q-browser-vs-common-module).
|
||
|
|
||
|
We _declare_ the contact component, directive, and pipe in the module `declarations`.
|
||
|
|
||
|
We _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.
|
||
|
### Refactor the _AppModule_
|
||
|
Return to the `AppModule` and remove everything specific to the contact feature set.
|
||
|
|
||
|
Delete the contact import statements.
|
||
|
Delete the contact declarations and contact providers.
|
||
|
Remove the `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` side-by-side with the previous version.
|
||
|
<md-tab-group>
|
||
|
|
||
|
<md-tab label="src/app/app.module.ts (v2)">
|
||
|
{@example 'ngmodule/ts/src/app/app.module.2.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/app.module.ts (v1)">
|
||
|
{@example 'ngmodule/ts/src/app/app.module.1b.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
</md-tab-group>
|
||
|
|
||
|
### ImprovementsThere's a lot to like in the revised `AppModule`
|
||
|
* It does not change as the _Contact_ domain grows.
|
||
|
* It only changes when we add new modules.
|
||
|
* It's simpler:
|
||
|
* Fewer import statements
|
||
|
* No `FormsModule` import
|
||
|
* No contact-specific declarations
|
||
|
* No `ContactService` provider
|
||
|
* No `HighlightDirective` conflict
|
||
|
|
||
|
Try this `ContactModule` version of the sample.
|
||
|
|
||
|
<live-example embedded plnkr="contact.2" img="devguide/ngmodule/contact-2-plunker.png">Try the live example.</live-example>
|
||
|
|
||
|
|
||
|
{@a lazy-load}
|
||
|
|
||
|
## Lazy loading modules with the Router
|
||
|
|
||
|
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 we won't discuss every line of code.
|
||
|
Examine and download the complete source for this version from the
|
||
|
<live-example plnkr="pre-shared.3" img="devguide/ngmodule/v3-plunker.png">live example.</live-example>Some facets of the current application merit discussion.
|
||
|
|
||
|
* The app has three feature modules: Contact, Hero, and Crisis.
|
||
|
* The Angular router helps users navigate among these modules.
|
||
|
* The `ContactComponent` is the default destination when the app starts.
|
||
|
* The `ContactModule` continues to be "eagerly" loaded when the application starts.
|
||
|
* `HeroModule` and the `CrisisModule` are lazy loaded.
|
||
|
|
||
|
<a id="app-component-template"></a>
|
||
|
Let's start at the top with the new `AppComponent` template:
|
||
|
a title, three links, and a `<router-outlet>`.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.component.3.ts' region='template'}
|
||
|
|
||
|
The `<app-contact>` element is gone; we're routing to the _Contact_ page now.
|
||
|
|
||
|
The `AppModule` has changed modestly:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.3.ts'}
|
||
|
|
||
|
|
||
|
Some file names bear a `.3` extension indicating
|
||
|
a difference with prior or future versions.
|
||
|
We'll explain differences that matter 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_](../guide/router.html#routing-module)
|
||
|
that handles the app's routing concerns.
|
||
|
|
||
|
### App routing
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app-routing.module.ts'}
|
||
|
|
||
|
The router is the subject of [its own page](router.html) so we'll skip lightly over the details and
|
||
|
concentrate on the intersection of Angular modules and routing.
|
||
|
|
||
|
This file defines three routes.
|
||
|
|
||
|
The first redirects the empty URL (e.g., `http://host.com/`)
|
||
|
to another route whose path is `contact` (e.g., `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.
|
||
|
We'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:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app-routing.module.ts' region='lazy-routes'}
|
||
|
|
||
|
|
||
|
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 `#`.
|
||
|
### RouterModule.forRoot
|
||
|
|
||
|
The `forRoot` static class method of the `RouterModule` with the provided configuration,
|
||
|
added to the `imports` array provides the routing concerns for the module.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app-routing.module.ts' region='forRoot'}
|
||
|
|
||
|
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_.
|
||
|
|
||
|
|
||
|
~~~ {.alert.is-critical}
|
||
|
|
||
|
Never call `RouterModule.forRoot` in a feature routing module.
|
||
|
|
||
|
~~~
|
||
|
|
||
|
Back in the root `AppModule`, we add the `AppRoutingModule` to its `imports` list,
|
||
|
and the app is ready to navigate.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.3.ts' region='imports'}
|
||
|
|
||
|
### Routing to a feature module
|
||
|
The `src/app/contact` folder holds a new file, `contact-routing.module.ts`.
|
||
|
It defines the `contact` route we mentioned a bit earlier and also provides a `ContactRoutingModule` like so:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact-routing.module.ts' region='routing'}
|
||
|
|
||
|
This time we pass the route list to the `forChild` method of the `RouterModule`.
|
||
|
It's only responsible for providing additional routes and is intended for feature modules.
|
||
|
|
||
|
|
||
|
~~~ {.alert.is-important}
|
||
|
|
||
|
Always call `RouterModule.forChild` in a feature routing module.
|
||
|
|
||
|
|
||
|
~~~
|
||
|
|
||
|
|
||
|
|
||
|
~~~ {.alert.is-helpful}
|
||
|
|
||
|
**_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](../cookbook/ngmodule-faq.html#q-for-root) if you write a similar module
|
||
|
that has both shared [_declarables_](../cookbook/ngmodule-faq.html#q-declarable) and services.
|
||
|
|
||
|
|
||
|
~~~
|
||
|
|
||
|
`ContactModule` has changed in two small but important details
|
||
|
<md-tab-group>
|
||
|
|
||
|
<md-tab label="src/app/contact/contact.module.3.ts">
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.module.3.ts' region='class'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/contact/contact.module.2.ts">
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.module.2.ts' region='class'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
</md-tab-group>
|
||
|
|
||
|
1. It imports the `ContactRoutingModule` object from `contact-routing.module.ts`
|
||
|
1. It no longer exports `ContactComponent`
|
||
|
|
||
|
Now that we navigate to `ContactComponent` with the router there's no reason to make it public.
|
||
|
Nor does it need a selector.
|
||
|
No template will ever again reference this `ContactComponent`.
|
||
|
It's gone from the [_AppComponent_ template](#app-component-template).
|
||
|
|
||
|
|
||
|
{@a hero-module}
|
||
|
### Lazy loaded routing to a module
|
||
|
|
||
|
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. Here's its file structure:
|
||
|
|
||
|
<aio-filetree>
|
||
|
|
||
|
<aio-folder>
|
||
|
hero
|
||
|
<aio-file>
|
||
|
hero-detail.component.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
hero-list.component.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
hero.component.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
hero.module.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
hero-routing.module.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
hero.service.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
<aio-file>
|
||
|
highlight.directive.ts
|
||
|
</aio-file>
|
||
|
|
||
|
|
||
|
</aio-folder>
|
||
|
|
||
|
|
||
|
</aio-filetree>
|
||
|
|
||
|
This is the child routing scenario familiar to readers of the [Router](router.html#child-routing-component) 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.
|
||
|
|
||
|
There's yet _another_ `HighlightDirective` that colors elements in yet a different shade.
|
||
|
We should [do something](#shared-module "Shared modules") about the repetition and inconsistencies.
|
||
|
We endure for now.
|
||
|
|
||
|
The `HeroModule` is a feature module like any other.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/hero/hero.module.3.ts' region='class'}
|
||
|
|
||
|
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. There's nothing more to say that's new.
|
||
|
|
||
|
<live-example embedded plnkr="pre-shared.3" img="devguide/ngmodule/v3-plunker.png">Try the live example.</live-example>
|
||
|
|
||
|
|
||
|
{@a shared-module}
|
||
|
|
||
|
## Shared modules
|
||
|
|
||
|
The app is shaping up.
|
||
|
One thing we don't like is carrying three different versions of the `HighlightDirective`.
|
||
|
And there's a bunch of other stuff cluttering the app folder level that could be tucked away.
|
||
|
|
||
|
Let's add a `SharedModule` to hold the common components, directives, and pipes
|
||
|
and share them with the modules that need them.
|
||
|
|
||
|
* create an `src/app/shared` folder
|
||
|
* move the `AwesomePipe` and `HighlightDirective` from `src/app/contact` to `src/app/shared`.
|
||
|
* delete the `HighlightDirective` classes from `src/app/` and `src/app/hero`
|
||
|
* create a `SharedModule` class to own the shared material
|
||
|
* update other feature modules to import `SharedModule`
|
||
|
|
||
|
Most of this is familiar blocking and tackling. Here is the `SharedModule`
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/shared/shared.module.ts'}
|
||
|
|
||
|
Some highlights
|
||
|
* It imports the `CommonModule` because its component needs common directives.
|
||
|
* It declares and exports the utility pipe, directive, and component classes as expected.
|
||
|
* It re-exports the `CommonModule` and `FormsModule`
|
||
|
|
||
|
### Re-exporting other modules
|
||
|
|
||
|
While reviewing our application, we noticed 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`.
|
||
|
|
||
|
We 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`.
|
||
|
|
||
|
### Why _TitleComponent_ isn't shared
|
||
|
|
||
|
`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.
|
||
|
|
||
|
<a id="no-shared-module-providers"></a>
|
||
|
### Why _UserService_ isn't shared
|
||
|
|
||
|
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 our 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.
|
||
|
We don't want each module to have its own separate instance.
|
||
|
Yet there is [a real danger](../cookbook/ngmodule-faq.html#q-why-it-is-bad) of that happening
|
||
|
if the `SharedModule` provides the `UserService`.
|
||
|
|
||
|
|
||
|
~~~ {.alert.is-critical}
|
||
|
|
||
|
Do **not** specify app-wide singleton `providers` in a shared module.
|
||
|
A lazy loaded module that imports that shared module will make its own copy of the service.
|
||
|
|
||
|
|
||
|
~~~
|
||
|
|
||
|
|
||
|
|
||
|
{@a core-module}
|
||
|
|
||
|
## The Core module
|
||
|
At the moment, our root folder is cluttered with the `UserService`
|
||
|
and the `TitleComponent` that only appears in the root `AppComponent`.
|
||
|
We did not include them in the `SharedModule` for reasons just explained.
|
||
|
|
||
|
Instead, we'll gather them in a single `CoreModule` that we **import _once_ when the app starts**
|
||
|
and _never import anywhere else_.
|
||
|
|
||
|
**Steps:**
|
||
|
|
||
|
* create an `src/app/core` folder
|
||
|
* move the `UserService` and `TitleComponent` from `src/app/` to `src/app/core`
|
||
|
* create a `CoreModule` class to own the core material
|
||
|
* update the `AppRoot` module to import `CoreModule`
|
||
|
|
||
|
Again, most of this is familiar blocking and tackling. The interesting part is the `CoreModule`
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/core/core.module.ts' region='v4'}
|
||
|
|
||
|
|
||
|
We're importing some extra symbols from the Angular core library that we're not using yet.
|
||
|
They'll become relevant later in this page.The `@NgModule` metadata should be familiar.
|
||
|
We declare the `TitleComponent` because this module _owns_ it and we export it
|
||
|
because `AppComponent` (which is in `AppModule`) displays the title in its template.
|
||
|
`TitleComponent` needs the Angular `NgIf` directive that we 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.
|
||
|
|
||
|
### Why bother?
|
||
|
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 we 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 (e.g., spinners, message toasts, and modal dialogs)
|
||
|
that appear only in the `AppComponent` template.
|
||
|
We 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_ are not shared.
|
||
|
|
||
|
We recommend collecting such single-use classes and hiding their gory details inside a `CoreModule`.
|
||
|
A simplified root `AppModule` imports `CoreModule` in its capacity as orchestrator of the application as a whole.
|
||
|
|
||
|
## Cleanup
|
||
|
Having refactored to a `CoreModule` and a `SharedModule`, it's time to cleanup the other modules.
|
||
|
|
||
|
### A trimmer _AppModule_
|
||
|
|
||
|
Here is the updated `AppModule` paired with version 3 for comparison:
|
||
|
<md-tab-group>
|
||
|
|
||
|
<md-tab label="src/app/app.module.ts (v4)">
|
||
|
{@example 'ngmodule/ts/src/app/app.module.ts' region='v4'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/app.module.ts (v3)">
|
||
|
{@example 'ngmodule/ts/src/app/app.module.3.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
</md-tab-group>
|
||
|
|
||
|
Notice that `AppModule` is ...
|
||
|
* a little smaller because many `src/app/root` classes have moved to other modules.
|
||
|
* stable because we'll add future components and providers to other modules, not this one.
|
||
|
* delegating to imported modules rather than doing work.
|
||
|
* focused on its main task, orchestrating the app as a whole.
|
||
|
|
||
|
### A trimmer _ContactModule_
|
||
|
Here is the new `ContactModule` paired with the prior version:
|
||
|
<md-tab-group>
|
||
|
|
||
|
<md-tab label="src/app/contact/contact.module.ts (v4)">
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.module.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
<md-tab label="src/app/contact/contact.module.ts (v3)">
|
||
|
{@example 'ngmodule/ts/src/app/contact/contact.module.3.ts'}
|
||
|
</md-tab>
|
||
|
|
||
|
|
||
|
</md-tab-group>
|
||
|
|
||
|
Notice that
|
||
|
* The `AwesomePipe` and `HighlightDirective` are gone.
|
||
|
* The imports include `SharedModule` instead of `CommonModule` and `FormsModule`
|
||
|
* This new version is leaner and cleaner.
|
||
|
|
||
|
<div class='l-hr'>
|
||
|
|
||
|
</div>
|
||
|
|
||
|
|
||
|
|
||
|
{@a core-for-root}
|
||
|
|
||
|
## Configure core services with _CoreModule.forRoot_
|
||
|
|
||
|
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](../api/core/index/ModuleWithProviders-interface.html) which is
|
||
|
a simple object with two properties:
|
||
|
* `ngModule` - the `CoreModule` class
|
||
|
* `providers` - the configured providers
|
||
|
|
||
|
The 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 we add explicitly to the `AppModule` providers takes precedence
|
||
|
over the providers of imported modules.Let's add a `CoreModule.forRoot` method that configures the core `UserService`.
|
||
|
|
||
|
We've extended the core `UserService` with an optional, injected `UserServiceConfig`.
|
||
|
If a `UserServiceConfig` exists, the `UserService` sets the user name from that config.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/core/user.service.ts' region='ctor'}
|
||
|
|
||
|
Here's `CoreModule.forRoot` that takes a `UserServiceConfig` object:
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/core/core.module.ts' region='for-root'}
|
||
|
|
||
|
Lastly, we call it _within the_ `imports` _list_ of the `AppModule`.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/app.module.ts' region='import-for-root'}
|
||
|
|
||
|
The app displays "Miss Marple" as the user instead of the default "Sherlock Holmes".
|
||
|
|
||
|
|
||
|
~~~ {.alert.is-important}
|
||
|
|
||
|
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 is likely to produce a runtime error.
|
||
|
|
||
|
Remember to _import_ the result; don't add it to any other `@NgModule` list.
|
||
|
|
||
|
|
||
|
~~~
|
||
|
|
||
|
|
||
|
<div class='l-hr'>
|
||
|
|
||
|
</div>
|
||
|
|
||
|
|
||
|
|
||
|
{@a prevent-reimport}
|
||
|
|
||
|
## Prevent reimport of the _CoreModule_
|
||
|
|
||
|
Only the root `AppModule` should import the `CoreModule`.
|
||
|
[Bad things happen](../cookbook/ngmodule-faq.html#q-why-it-is-bad) if a lazy loaded module imports it.
|
||
|
|
||
|
We could _hope_ that no developer makes that mistake.
|
||
|
Or we can guard against it and fail fast by adding the following `CoreModule` constructor.
|
||
|
|
||
|
{@example 'ngmodule/ts/src/app/core/core.module.ts' region='ctor'}
|
||
|
|
||
|
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 we 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.### Conclusion
|
||
|
|
||
|
You made it! You can examine and download the complete source for this final version from the live example.
|
||
|
<live-example embedded img="devguide/ngmodule/final-plunker.png"></live-example>
|
||
|
|
||
|
### Frequently Asked Questions
|
||
|
|
||
|
Now that you understand Angular Modules, you may be interested
|
||
|
in the companion [Angular Module FAQs](../cookbook/ngmodule-faq.html "Angular Module FAQs") cookbook
|
||
|
with its ready answers to specific design and implementation questions.
|