NgModules are a core concept in Angular that are part of every application and help to wire up some important details for the compiler and application runtime. They’re especially useful for organizing code into features, lazy loading routes, and creating reusable libraries.
In this guide, we're going to cover the primary uses of NgModules with some examples to show you how to use them in your Angular projects! This guide assumes you have a working knowledge of Angular.
JavaScript Modules Aren’t NgModules
Let's clear the air first about what JavaScript modules are (sometimes called ES6 modules). They’re a language construct that makes it easier to organize your code.
At their most basic, Javascript modules are JavaScript files that contain either the import
or export
keywords, and which cause the objects defined inside of that file to be private unless you export it. I encourage you to review the link above for a deeper understanding, but essentially this is a way to organize your code and easily share it, without relying on the dreaded global scope.
When you create an Angular application with TypeScript, any time you use import
or export
in your source, it’s treated as a JavaScript module. TypeScript is able to handle the module loading for you.
Note: to help keep things clear in this article, I'll always refer to JavaScript modules and NgModules by their full names.
The Basic NgModule, the AppModule
Let's start by looking at a basic NgModule that exists in every Angular application, the AppModule
(which is generated by default in any new Angular application). It looks something like you see here:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Angular uses decorators to define metadata it needs to know about during compile time. To define an NgModue, you simply add the @NgModule()
decorator above a class. The class may not always be empty, but often it is. However, you'll need to define an object with some properties for the NgModule to do anything.
When the application bootstraps, it needs to be given an NgModule to instantiate. If you look in the main file of your application (also typically called main.ts
), you'll see platformBrowserDynamic().bootstrapModule(AppModule)
, which is how the application registers and initiates the AppModule
(which can be named anything, but is almost always named this).
The Properties of NgModule
The NgModule API documentation page outlines the properties that you can pass when defining an NgModule, but we'll cover them here as well. They’re all optional, but you’ll need to define values for at least one of them for the NgModule to do anything.
providers
The providers
is an array that contains the list of any providers (injectable services) that are available for this NgModule. Providers have a scope, and if they’re listed in a lazy-loaded NgModule, they’re not available outside of that NgModule.
declarations
The declarations
array should contain a list of any directives, components, or pipes that this NgModule defines. This makes it possible for the compiler to find these items and ensure they’re bundled properly. If this is the root NgModule, then declarations are available for all NgModules. Otherwise, they’re only visible to the same NgModule.
imports
If your NgModule depends on any other objects from another NgModule, you'll have to add it to the imports
array. This ensures that the compiler and dependency injection system know about the imported items.
exports
Using the exports
array, you can define which directives, components, and pipes are available for any NgModule that imports this NgModule. For example, in a UI library you’d export all of the components that compose the library.
entryComponents
Any component that needs to be loaded at runtime has to be added to the list of entryComponents
. Essentially, this will create the component factory and store it for when it needs to be loaded dynamically. You can learn more about how to dynamically load components from the documentation.
bootstrap
You can define any number of components to bootstrap when the app is first loaded. Usually you only need to bootstrap the main root component (usually called the AppComponent
), but if you had more than one root component, each would be declared here. By adding a component to the bootstrap
array, it’s also added to the list of entryComponents
and precompiled.
schemas
Schemas are a way to define how Angular compiles templates, and if it will throw an error when it finds elements that aren't standard HTML or known components. By default, Angular throws an error when it finds an element in a template that it doesn't know, but you can change this behavior by setting the schema to either NO_ERRORS_SCHEMA (to allow all elements and properties) or CUSTOM_ELEMENTS_SCHEMA (to allow any elements or properties with a -
in their name).
id
This property allows you to give an NgModule a unique ID, which you can use to retrieve a module factory reference. This is a rare use case currently.
NgModule Examples
To illustrate the way NgModule is used with Angular, let's look at a set of examples that show you how to handle various use cases easily.
Feature NgModules
The most basic use case for NgModules besides the AppModule
is for Feature NgModules (usually called feature modules, but trying to keep the terms consistent). They help separate individual parts of your application, and are highly recommended. In most ways, they’re the same as the main App NgModule. Let's take a look at a basic Feature NgModule:
@NgModule({
declarations: [
ForumComponent,
ForumsComponent,
ThreadComponent,
ThreadsComponent
],
imports: [
CommonModule,
FormsModule,
],
exports: [
ForumsComponent
]
providers: [
ForumsService
]
})
export class ForumsModule { }
This simple Feature NgModule defines four components, one provider, and imports two modules that are required by the components and service. Together, these comprise the necessary pieces for the forums section of an application.
The items in providers
are available to any NgModule that imports the ForumsModule
to be injected, but it’s important to understand that each NgModule will get its own instance of that service. This is different from providers listed in the root NgModule, from which you’ll always get the same instance (unless its reprovided). This is where it’s important to understand dependency injection, particularly hierarchical dependency injection. It’s easy to think you'll get the same instance of a service and change properties on it, but never see the changes elsewhere in the application.
As we learned earlier, the items in declarations
are not actually available to be used in other NgModules, because they’re private to this NgModule. To fix this, you can optionally export those declarations you wish to consume in other NgModules, like in this snippet where it exports just the ForumsComponent
. Now, in any other Feature NgModules, you could put <app-forums></app-forums>
(or whatever the selector for the component is) to display the ForumsComponent
in a template.
Another key difference is that ForumsModule
imports the CommonModule instead of the BrowserModule. The BrowserModule
should only be imported at the root NgModule, but the CommonModule
contains the core Angular directives and pipes (such as NgFor
and the Date
pipe). If your Feature NgModule doesn't use any of those features, it wouldn't actually need the CommonModule
.
Now, when you want to consume the ForumsModule
in your project, you need to import it into your AppModule
like you see here:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ForumsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
This NgModule is then imported into the main AppModule
to load it properly, which includes the items in the ForumsModule
providers array and any exported items for consumption in your application.
When you use the Angular CLI, you can easily generate Feature NgModules by running the generator for a new NgModule:
ng generate module path/to/module/feature
You can organize your Feature NgModules any way you see fit, but the general recommendation is to group similar things that are used on the same view. I try to make a small number of Feature NgModules to hold the commonly shared things, and then focus more on NgModules for each major feature of the application.
Continue reading %Using Angular NgModules for Reusable Code and More%
by Jeremy Wilken via SitePoint
No comments:
Post a Comment