In this chapter, I describe the last of the Angular building blocks: modules. In the first part of the chapter, I describe the root module, which every Angular application uses to describe the configuration of the application to Angular. In the second part of the chapter, I describe feature modules, which are used to add structure to an application so that related features can be grouped as a single unit. Table 21-1 puts modules in context.

Table 21-1 Putting Modules in Context

Table 21-2 summarizes the chapter.

Table 21-2 Chapter Summary

Preparing the Example Project

As with the other chapters in this part of the book, I am going to use the example project that was created in Chapter 11 and has been expanded and extended in each chapter since.

Tip

You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/Apress/pro-angular-6 .

To prepare for this chapter, I have removed some functionality from the component templates. Listing 21-1 shows the template for the product table, in which I have commented out the elements for the discount editor and display components.

Listing 21-1 The Contents of the productTable.component.html File in the src/app Folder

<table class="table table-sm table-bordered table-striped">   <tr>     <th></th>     <th>Name</th>     <th>Category</th>     <th>Price</th>     <th>Discount</th>     <th></th>   </tr>   <tr *paFor="let item of getProducts(); let i = index;             let odd = odd; let even = even" [class.bg-info]="odd"       [class.bg-warning]="even">     <td style="vertical-align:middle">{{i + 1}}</td>     <td style="vertical-align:middle">{{item.name}}</td>     <td style="vertical-align:middle">{{item.category}}</td>     <td style="vertical-align:middle">       {{item.price | discount | currency:"USD":"symbol" }}     </td>     <td style="vertical-align:middle" [pa-price]="item.price"         #discount="discount">       {{ discount.discountAmount | currency:"USD":"symbol"}}     </td>     <td class="text-center">       <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">         Delete       </button>     </td>   </tr> </table> <!--<paDiscountEditor></paDiscountEditor>--> <!--<paDiscountDisplay></paDiscountDisplay>-->

Listing 21-2 shows the template from the product form component, in which I have commented out the elements that I used to demonstrate the difference between providers for view children and content children in Chapter 20.

Listing 21-2 The Contents of the productForm.component.html File in the src/app Folder

<form novalidate [formGroup]="form" (ngSubmit)="submitForm(form)">   <div class="form-group" *ngFor="let control of form.productControls">     <label>{{control.label}}</label>     <input class="form-control"            [(ngModel)]="newProduct[control.modelProperty]"            name="{{control.modelProperty}}"            formControlName="{{control.modelProperty}}" />     <ul class="text-danger list-unstyled"         *ngIf="(formSubmitted || control.dirty) && !control.valid">       <li *ngFor="let error of control.getValidationMessages()">         {{error}}       </li>     </ul>   </div>   <button class="btn btn-primary" type="submit"           [disabled]="formSubmitted && !form.valid"           [class.btn-secondary]="formSubmitted && !form.valid">     Create   </button> </form> <!--<div class="bg-info text-white m-2 p-2">   View Child Value: <span paDisplayValue></span> </div> <div class="bg-info text-white m-2 p-2">   Content Child Value: <ng-content></ng-content> </div>-->

Run the following command in the example folder to start the Angular development tools:

ng serve

Open a new browser window and navigate to http://localhost:4200 to see the content shown in Figure 21-1.

Figure 21-1
figure 1

Running the example application

Understanding the Root Module

Every Angular has at least one module, known as the root module. The root module is conventionally defined in a file called app.module.ts in the src/app folder, and it contains a class to which the @NgModule decorator has been applied. Listing 21-3 shows the root module from the example application.

Listing 21-3 The Root Module in the app.module.ts File in the src/app Folder

import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { ProductComponent } from "./component"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { PaAttrDirective } from "./attr.directive"; import { PaModel } from "./twoway.directive"; import { PaStructureDirective } from "./structure.directive"; import { PaIteratorDirective } from "./iterator.directive"; import { PaCellColor } from "./cellColor.directive"; import { PaCellColorSwitcher } from "./cellColorSwitcher.directive"; import { ProductTableComponent } from "./productTable.component"; import { ProductFormComponent } from "./productForm.component"; import { PaAddTaxPipe } from "./addTax.pipe"; import { PaCategoryFilterPipe } from "./categoryFilter.pipe"; import { PaDiscountDisplayComponent } from "./discountDisplay.component"; import { PaDiscountEditorComponent } from "./discountEditor.component"; import { DiscountService } from "./discount.service"; import { PaDiscountPipe } from "./discount.pipe"; import { PaDiscountAmountDirective } from "./discountAmount.directive"; import { SimpleDataSource } from "./datasource.model"; import { Model } from "./repository.model"; import {   LogService, LOG_SERVICE, SpecialLogService,   LogLevel, LOG_LEVEL } from "./log.service"; import { VALUE_SERVICE, PaDisplayValueDirective } from "./valueDisplay.directive"; @NgModule({   imports: [BrowserModule, FormsModule, ReactiveFormsModule],   declarations: [ProductComponent, PaAttrDirective, PaModel,     PaStructureDirective, PaIteratorDirective,     PaCellColor, PaCellColorSwitcher, ProductTableComponent,     ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,     PaDiscountDisplayComponent, PaDiscountEditorComponent,     PaDiscountPipe, PaDiscountAmountDirective, PaDisplayValueDirective],   providers: [DiscountService, SimpleDataSource, Model, LogService,     { provide: VALUE_SERVICE, useValue: "Apples" }],   bootstrap: [ProductComponent] }) export class AppModule { }

There can be multiple modules in a project, but the root module is the one used in the bootstrap file, which is conventionally called main.ts and is defined in the src folder. Listing 21-4 shows the main.ts file for the example project.

Listing 21-4 The Angular Bootstrap in the main.ts File in the src Folder

import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) {   enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule)   .catch(err => console.log(err));

Angular applications can be run in different environments, such as web browsers and native application containers. The job of the bootstrap file is to select the platform and identify the root module. The platformBrowserDynamic method creates the browser runtime, and the bootstrapModule method is used to specify the module, which is the AppModule class from Listing 21-3.

When defining the root module, the @NgModule decorator properties described in Table 21-3 are used. (There are additional decorator properties, which are described later in the chapter.)

Table 21-3 The @NgModule Decorator Root Module Properties

Understanding the imports Property

The imports property is used to list the other modules that the application requires. In the example application, these are all modules provided by the Angular framework.

... imports: [BrowserModule, FormsModule, ReactiveFormsModule], ...

The BrowserModule provides the functionality required to run Angular applications in web browsers. The other two modules provide support for working with HTML forms and model-based forms, as described in Chapter 14. There are other Angular modules, which are introduced in later chapters.

The imports property is also used to declare dependencies on custom modules, which are used to manage complex Angular applications and to create units of reusable functionality. I explain how custom modules are defined in the “Creating Feature Modules” section.

Understanding the declarations Property

The declarations property is used to provide Angular with a list of the directives, components, and pipes that the application requires, known collectively as the declarable classes. The declarations property in the example project root module contains a long list of classes, each of which is available for use elsewhere in the application only because it is listed here.

... declarations: [ProductComponent, PaAttrDirective, PaModel,     PaStructureDirective, PaIteratorDirective,     PaCellColor, PaCellColorSwitcher, ProductTableComponent,     ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,     PaDiscountDisplayComponent, PaDiscountEditorComponent,     PaDiscountPipe, PaDiscountAmountDirective, PaDisplayValueDirective], ...

Notice that the built-in declarable classes, such as the directives described in Chapter 13 and the pipes described in Chapter 18, are not included in the declarations property for the root module. This is because they are part of the BrowserModule module, and when you add a module to the imports property, its declarable classes are automatically available for use in the application.

Understanding the providers Property

The providers property is used to define the service providers that will be used to resolve dependencies when there are no suitable local providers available. The use of providers for services is described in detail in Chapters 19 and 20.

Understanding the bootstrap Property

The bootstrap property specifies the root component or components for the application. When Angular process the main HTML document, which is conventionally called index.html, it inspects the root components and applies them using the value of the selector property in the @Component decorators.

Tip

The components listed in the bootstrap property must also be included in the declarations list.

Here is the bootstrap property from the example project’s root module:

... bootstrap: [ProductComponent] ...

The ProductComponent class provides the root component, and its selector property specifies the app element, as shown in Listing 21-5.

Listing 21-5 The Root Component in the component.ts File in the src/app Folder

import { Component } from "@angular/core"; @Component({   selector: "app",   templateUrl: "template.html" }) export class ProductComponent { }

When I started the example project in Chapter 11, the root component had a lot of functionality. But since the introduction of additional components, the role of this component has been reduced, and it is now essentially a placeholder that tells Angular to project the contents of the app/template.html file into the app element in the HTML document, which allows the components that do the real work in the application to be loaded.

There is nothing wrong with this approach, but it does mean the root component in the application doesn’t have a great deal to do. If this kind of redundancy feels untidy, then you can specify multiple root components in the root module, and all of them will be used to target elements in the HTML document. To demonstrate, I have removed the existing root component from the root module’s bootstrap property and replaced it with the component classes that are responsible for the product form and the product table, as shown in Listing 21-6.

Listing 21-6 Specifying Multiple Root Components in the app.module.ts File in the src/app Folder

... @NgModule({   imports: [BrowserModule, FormsModule, ReactiveFormsModule],   declarations: [ProductComponent, PaAttrDirective, PaModel,     PaStructureDirective, PaIteratorDirective,     PaCellColor, PaCellColorSwitcher, ProductTableComponent,     ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,     PaDiscountDisplayComponent, PaDiscountEditorComponent,     PaDiscountPipe, PaDiscountAmountDirective, PaDisplayValueDirective],   providers: [DiscountService, SimpleDataSource, Model, LogService,     { provide: VALUE_SERVICE, useValue: "Apples" }],   bootstrap: [ProductFormComponent, ProductTableComponent] }) export class AppModule { } ...

Listing 21-7 reflects the change in the root components in the main HTML document.

Listing 21-7 Changing the Root Component Elements in the index.html File in the src Folder

<!doctype html> <html lang="en"> <head>   <meta charset="utf-8">   <title>Example</title>   <base href="/">   <meta name="viewport" content="width=device-width, initial-scale=1">   <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body class="m-2 row">     <div class="col-8 p-2">         <paProductTable></paProductTable>     </div>     <div class="col-4 p-2">         <paProductForm></paProductForm>     </div> </body> </html>

I have reversed the order in which these components appear compared to previous examples, just to create a detectable change in the application’s layout. When all the changes are saved and the browser has reloaded the page, you will see the new root components displayed, as illustrated by Figure 21-2.

Figure 21-2
figure 2

Using multiple root components

The module’s service providers are used to resolve dependencies for all root components. In the case of the example application, this means there is a single Model service object that is shared throughout the application and that allows products created with the HTML form to be displayed automatically in the table, even though these components have been promoted to be root components.

Creating Feature Modules

The root module has become increasingly complex as I added features in earlier chapters, with a long list of import statements to load JavaScript modules and a set of classes in the declararations property of the @NgModule decorator that spans several lines, as shown in Listing 21-8.

Listing 21-8 The Contents of the app.module.ts File in the src/app Folder

import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { ProductComponent } from "./component"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { PaAttrDirective } from "./attr.directive"; import { PaModel } from "./twoway.directive"; import { PaStructureDirective } from "./structure.directive"; import { PaIteratorDirective } from "./iterator.directive"; import { PaCellColor } from "./cellColor.directive"; import { PaCellColorSwitcher } from "./cellColorSwitcher.directive"; import { ProductTableComponent } from "./productTable.component"; import { ProductFormComponent } from "./productForm.component"; import { PaAddTaxPipe } from "./addTax.pipe"; import { PaCategoryFilterPipe } from "./categoryFilter.pipe"; import { PaDiscountDisplayComponent } from "./discountDisplay.component"; import { PaDiscountEditorComponent } from "./discountEditor.component"; import { DiscountService } from "./discount.service"; import { PaDiscountPipe } from "./discount.pipe"; import { PaDiscountAmountDirective } from "./discountAmount.directive"; import { SimpleDataSource } from "./datasource.model"; import { Model } from "./repository.model"; import {   LogService, LOG_SERVICE, SpecialLogService,   LogLevel, LOG_LEVEL } from "./log.service"; import { VALUE_SERVICE, PaDisplayValueDirective } from "./valueDisplay.directive"; @NgModule({   imports: [BrowserModule, FormsModule, ReactiveFormsModule],   declarations: [ProductComponent, PaAttrDirective, PaModel,     PaStructureDirective, PaIteratorDirective,     PaCellColor, PaCellColorSwitcher, ProductTableComponent,     ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,     PaDiscountDisplayComponent, PaDiscountEditorComponent,     PaDiscountPipe, PaDiscountAmountDirective, PaDisplayValueDirective],   providers: [DiscountService, SimpleDataSource, Model, LogService,     { provide: VALUE_SERVICE, useValue: "Apples" }],   bootstrap: [ProductFormComponent, ProductTableComponent] }) export class AppModule { }

Feature modules are used to group related functionality so that it can be used as a single entity, just like the Angular modules such as BrowserModule. When I need to use the features for working with forms, for example, I don’t have to add import statements and declarations entries for each individual directive, component, or pipe. Instead, I just add BrowserModule to the decorator’s imports property, and all of the functionality it contains is available throughout the application.

When you create a feature module, you can choose to focus on an application function or elect to group a set of related building blocks that provide your application’s infrastructure. I’ll do both in the sections that follow because they work in slightly different ways and have different considerations. Feature modules use the same @NgModule decorator but with an overlapping set of configuration properties, some of which are new and some of which are used in common with the root module but have a different effect. I explain how these properties are used in the following sections, but Table 21-4 provides a summary for quick reference.

Table 21-4 The @NgModule Decorator Properties for Feature Modules

Creating a Model Module

The term model module might be a tongue twister, but it is generally a good place to start when refactoring an application using feature modules because just about every other building block in the application depends on the model.

The first step is to create the folder that will contain the module. Module folders are defined within the src/app folder and are given a meaningful name. For this module, I created an src/app/model folder.

The naming conventions used for Angular files makes it easy to move and delete multiple files. Run the following commands in the example folder to move the files (they will work in Windows PowerShell, Linux, and macOS):

mv src/app/*.model.ts src/app/model/ mv src/app/limit.formvalidator.ts src/app/model/

The result is that the files listed in Table 21-5 are moved to the model folder.

Table 21-5 The File Moves Required for the Module

If you try to build the project once you have moved the files, the TypeScript compiler will list a series of compiler errors because some of the key declarable classes are unavailable. I’ll deal with these problems shortly.

Creating the Module Definition

The next step is to define a module that brings together the functionality in the files that have been moved to the new folder. I added a file called model.module.ts in the src/app/model folder and defined the module shown in Listing 21-9.

Listing 21-9 The Contents of the model.module.ts File in the src/app/model Folder

import { NgModule } from "@angular/core"; import { SimpleDataSource } from "./datasource.model"; import { Model } from "./repository.model"; @NgModule({     providers: [Model, SimpleDataSource] }) export class ModelModule { }

This purpose of a feature module is to selectively expose the contents of the module folder to the rest of the application. The @NgModule decorator for this module uses only the providers property to define class providers for the Model and SimpleDataSource services. When you use providers in a feature module, they are registered with the root module’s injector, which means they are available throughout the application, which is exactly what is required for the data model in the example application.

Tip

A common mistake is to assume that services defined in a module are accessible only to the classes within that module. There is no module scope in Angular. Providers defined by a feature module are used as though they were defined by the root module. Local providers defined by directives and components in the feature module are available to their view and content children even if they are defined in other modules.

Updating the Other Classes in the Application

Moving classes into the model folder has broken import statements in other parts of the application, which depend on either the Product or ProductFormGroup class. The next step is to update those import statements to point to the new module. There are four affected files: attr.directive.ts, categoryFilter.pipe.ts, productForm.component.ts, and productTable.component.ts. Listing 21-10 shows the changes required to the attr.directive.ts file.

Listing 21-10 Updating the Import Reference in the attr.directive.ts File in the src/app Folder

import { Directive, ElementRef, Attribute, Input,     SimpleChange, Output, EventEmitter, HostListener, HostBinding } from "@angular/core"; import { Product } from "./model/product.model"; @Directive({     selector: "[pa-attr]" }) export class PaAttrDirective {     // ...statements omitted for brevity... }

The only change that is required is to update the path used in the import statement to reflect the new location of the code file. Listing 21-11 shows the same change applied to the categoryFilter.pipe.ts file.

Listing 21-11 Updating the Import Reference in the categoryFilter.pipe.ts File in the src/app Folder

import { Pipe } from "@angular/core"; import { Product } from "./model/product.model"; @Pipe({     name: "filter",     pure: false }) export class PaCategoryFilterPipe {     transform(products: Product[], category: string): Product[] {         return category == undefined ?             products : products.filter(p => p.category == category);     } }

Listing 21-12 updates the import statements in the productForm.component.ts file.

Listing 21-12 Updating Import Paths in the productForm.component.ts File in the src/app Folder

import { Component, Output, EventEmitter, ViewEncapsulation,     Inject, SkipSelf } from "@angular/core"; import { Product } from "./model/product.model"; import { ProductFormGroup } from "./model/form.model"; import { Model } from "./model/repository.model"; import { VALUE_SERVICE } from "./valueDisplay.directive"; @Component({     selector: "paProductForm",     templateUrl: "productForm.component.html",     viewProviders: [{ provide: VALUE_SERVICE, useValue: "Oranges" }] }) export class ProductFormComponent {     // ...statements omitted for brevity... }

Listing 21-13 updates the paths in the final file, productTable.component.ts.

Listing 21-13 Updating Import Paths in the productTable.component.ts File in the src/app Folder

import { Component, Input, ViewChildren, QueryList } from "@angular/core"; import { Model } from "./model/repository.model"; import { Product } from "./model/product.model"; import { DiscountService } from "./discount.service"; @Component({     selector: "paProductTable",     templateUrl: "productTable.component.html" }) export class ProductTableComponent {     // ...statements omitted for brevity... }

USING A JAVASCRIPT MODULE WITH AN ANGULAR MODULE

Creating an Angular module allows related application features to be grouped together but still requires that each one is imported from its own file when it is needed elsewhere in the application, as you have seen in the listings in this section.

You can also define a JavaScript module that exports the public-facing features of the Angular module so they can be accessed with the same kind of import statement that is used for the @angular/core module, for example. To use a JavaScript module, add a file called index.ts in the module folder alongside the TypeScript file that defines the Angular module, which is the src/app/model folder for the examples in this section. For each of the application features that you want to use outside of the application, add an export...from statement, like this:

export { ModelModule } from "./model.module"; export { Product } from "./product.model"; export { ProductFormGroup, ProductFormControl } from "./form.model"; export { SimpleDataSource } from "./datasource.model"; export { LimitValidator } from "./limit.formvalidator"; export { Model } from "./repository.model";

These statements export the contents of the individual TypeScript files. You can then import the features you require without having to specify individual files, like this:

... import { Component, Output, EventEmitter, ViewEncapsulation,     Inject, SkipSelf } from "@angular/core"; import { Product, ProductFormGroup, Model } from "./model"; import { VALUE_SERVICE } from "./valueDisplay.directive"; ...

Using the file name index.ts means that you only have to specify the name of the folder in the import statement, producing a result that is neater and more consistent with the Angular core packages.

That said, I don’t use this technique in my own projects. Using an index.ts file means that you have to remember to add every feature to both the Angular and JavaScript modules, which is an extra step that I often forget to do. Instead, I use the approach shown in this chapter and import directly from the files that contain the application’s features.

Updating the Root Module

The final step is to update the root module so that the services defined in the feature module are made available throughout the application. Listing 21-14 shows the required changes.

Listing 21-14 Updating the Root Module in the app.module.ts File in the src/app Folder

import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { ProductComponent } from "./component"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { PaAttrDirective } from "./attr.directive"; import { PaModel } from "./twoway.directive"; import { PaStructureDirective } from "./structure.directive"; import { PaIteratorDirective } from "./iterator.directive"; import { PaCellColor } from "./cellColor.directive"; import { PaCellColorSwitcher } from "./cellColorSwitcher.directive"; import { ProductTableComponent } from "./productTable.component"; import { ProductFormComponent } from "./productForm.component"; import { PaAddTaxPipe } from "./addTax.pipe"; import { PaCategoryFilterPipe } from "./categoryFilter.pipe"; import { PaDiscountDisplayComponent } from "./discountDisplay.component"; import { PaDiscountEditorComponent } from "./discountEditor.component"; import { DiscountService } from "./discount.service"; import { PaDiscountPipe } from "./discount.pipe"; import { PaDiscountAmountDirective } from "./discountAmount.directive"; import { ModelModule } from "./model/model.module"; import {     LogService, LOG_SERVICE, SpecialLogService,     LogLevel, LOG_LEVEL } from "./log.service"; import { VALUE_SERVICE, PaDisplayValueDirective } from "./valueDisplay.directive"; @NgModule({     imports: [BrowserModule, FormsModule, ReactiveFormsModule, ModelModule],     declarations: [ProductComponent, PaAttrDirective, PaModel,         PaStructureDirective, PaIteratorDirective,         PaCellColor, PaCellColorSwitcher, ProductTableComponent,         ProductFormComponent, PaAddTaxPipe, PaCategoryFilterPipe,         PaDiscountDisplayComponent, PaDiscountEditorComponent,         PaDiscountPipe, PaDiscountAmountDirective, PaDisplayValueDirective],     providers: [DiscountService, LogService,         { provide: VALUE_SERVICE, useValue: "Apples" }],     bootstrap: [ProductFormComponent, ProductTableComponent] }) export class AppModule { }

I imported the feature module and added it to the root module’s imports list. Since the feature module defines providers for Model and SimpleDataSource, I removed the entries from the root module’s providers list and removed the associated import statements.

Once you have saved the changes, you can run ng serve to start the Angular development tools. The application will compile, and the revised root module will provide access to the model service. There are no visible changes to the content displayed in the browser, and the changes are limited to the structure of the project.

Creating a Utility Feature Module

A model module is a good place to start because it demonstrates the basic structure of a feature module and how it relates to the root module. The impact on the application was slight, however, and not a great deal of simplification was achieved.

The next step up in complexity is a utility feature module, which groups together all of the common functionality in the application, such as pipes and directives. In a real project, you might be more selective about how you group these types of building block together so that there are several modules, each containing similar functionality. For the example application, I am going to move all of the pipes, directives, and services into a single module.

Creating the Module Folder and Moving the Files

As with the previous module, the first step is to create the folder. For this module, I created a folder called src/app/common. Run the following commands in the example folder to move the TypeScript files for the pipes and directives:

mv src/app/*.pipe.ts src/app/common/ mv src/app/*.directive.ts src/app/common/

These commands should work in Windows PowerShell, Linux, and macOS. Some of the directives and pipes in the application rely on the DiscountService and LogServices classes, which is provided to them through dependency injection. Run the following command in the example folder to move the TypeScript file for the service into the module folder:

mv src/app/*.service.ts src/app/common/

The result is that the files listed in Table 21-6 are moved to the common module folder.

Table 21-6 The File Moves Required for the Module

Updating the Classes in the New Module

Some of the classes that have been moved into the new folder have import statements that have to be updated to reflect the new path to the model module. Listing 21-15 shows the change required to the attr.directive.ts file.

Listing 21-15 Updating the Imports in the attr.directive.ts File in the src/app/common Folder

import {     Directive, ElementRef, Attribute, Input,     SimpleChange, Output, EventEmitter, HostListener, HostBinding }     from "@angular/core"; import { Product } from "../model/product.model"; @Directive({     selector: "[pa-attr]" }) export class PaAttrDirective {     // ...statements omitted for brevity... }

Listing 21-16 shows the corresponding change to the categoryFilter.pipe.ts file.

Listing 21-16 Updating the Imports in the categoryFilter.pipe.ts File in the src/app/common Folder

import { Pipe } from "@angular/core"; import { Product } from "../model/product.model"; @Pipe({     name: "filter",     pure: false }) export class PaCategoryFilterPipe {     transform(products: Product[], category: string): Product[] {         return category == undefined ?             products : products.filter(p => p.category == category);     } }

Creating the Module Definition

The next step is to define a module that brings together the functionality in the files that have been moved to the new folder. I added a file called common.module.ts in the src/app/common folder and defined the module shown in Listing 21-17.

Listing 21-17 The Contents of the common.module.ts File in the src/app/common Folder

import { NgModule } from "@angular/core"; import { PaAddTaxPipe } from "./addTax.pipe"; import { PaAttrDirective } from "./attr.directive"; import { PaCategoryFilterPipe } from "./categoryFilter.pipe"; import { PaCellColor } from "./cellColor.directive"; import { PaCellColorSwitcher } from "./cellColorSwitcher.directive"; import { PaDiscountPipe } from "./discount.pipe"; import { PaDiscountAmountDirective } from "./discountAmount.directive"; import { PaIteratorDirective } from "./iterator.directive"; import { PaStructureDirective } from "./structure.directive"; import { PaModel } from "./twoway.directive"; import { VALUE_SERVICE, PaDisplayValueDirective} from "./valueDisplay.directive"; import { DiscountService } from "./discount.service"; import { LogService } from "./log.service"; import { ModelModule } from "../model/model.module"; @NgModule({     imports: [ModelModule],     providers: [LogService, DiscountService,         { provide: VALUE_SERVICE, useValue: "Apples" }],     declarations: [PaAddTaxPipe, PaAttrDirective, PaCategoryFilterPipe,         PaCellColor, PaCellColorSwitcher, PaDiscountPipe,         PaDiscountAmountDirective, PaIteratorDirective, PaStructureDirective,         PaModel, PaDisplayValueDirective],     exports: [PaAddTaxPipe, PaAttrDirective, PaCategoryFilterPipe,         PaCellColor, PaCellColorSwitcher, PaDiscountPipe,         PaDiscountAmountDirective, PaIteratorDirective, PaStructureDirective,         PaModel, PaDisplayValueDirective] }) export class CommonModule { }

This is a more complex module than the one required for the data model. In the sections that follow, I describe the values that are used for each of the decorator’s properties.

Understanding the Imports

Some of the directives and pipes in the module depend on the services defined in the model module, created earlier in this chapter. To ensure that the features in that module are available, I have added to the common module’s imports property.

Understanding the Providers

The providers property ensures that the services that the directives and pipes in the feature module have access to the services they require. This means adding class providers to create LogService and DiscountService services, which will be added to the root module’s providers when the module is loaded. Not only will the services be available to the directives and pipes in the common module; they will also be available throughout the application.

Understanding the Declarations

The declarations property is used to provide Angular with a list of the directives and pipes (and components, if there are any) in the module. In a feature module, this property has two purposes: it enables the declarable classes for use in any templates contained within the module, and it allows a module to make those declarable classes available outside of the module. I create a module that contains template content later in this chapter, but for this module, the value of the declarations property is that it must be used in order to prepare for the exports property, described in the next section.

Understanding the Exports

For a module that contains directives and pipes intended for use elsewhere in the application, the exports property is the most important in the @NgModule decorator because it defines the set of directives, components, and pipes that the module provides for use when it is imported elsewhere in the application. The exports property can contain individual classes and module types, although both must already be listed in the declarations or imports property. When the module is imported, the types listed behave as though they had been added to the importing module’s declarations property.

Updating the Other Classes in the Application

Now that the module has been defined, I can update the other files in the application that contain import statements for the types that are now part of the common module. Listing 21-18 shows the changes required to the discountDisplay.component.ts file.

Listing 21-18 Updating the Import in the discountDisplay.component.ts File in the src/app Folder

import { Component, Input } from "@angular/core"; import { DiscountService } from "./common/discount.service"; @Component({     selector: "paDiscountDisplay",     template: `<div class="bg-info p-2">                 The discount is {{discounter.discount}}                </div>` }) export class PaDiscountDisplayComponent {     constructor(private discounter: DiscountService) { } }

Listing 21-19 shows the changes to the discountEditor.component.ts file.

Listing 21-19 Updating the Import Reference in the discountEditor.component.ts File in the src/app Folder

import { Component, Input } from "@angular/core"; import { DiscountService } from "./common/discount.service"; @Component({     selector: "paDiscountEditor",     template: `<div class="form-group">                    <label>Discount</label>                    <input [(ngModel)]="discounter.discount"                         class="form-control" type="number" />                </div>` }) export class PaDiscountEditorComponent {     constructor(private discounter: DiscountService) { } }

Listing 21-20 shows the changes to the productForm.component.ts file.

Listing 21-20 Updating the Import Reference in the productForm.component.ts File in the src/app Folder

import { Component, Output, EventEmitter, ViewEncapsulation,     Inject, SkipSelf } from "@angular/core"; import { Product } from "./model/product.model"; import { ProductFormGroup } from "./model/form.model"; import { Model } from "./model/repository.model"; import { VALUE_SERVICE } from "./common/valueDisplay.directive"; @Component({     selector: "paProductForm",     templateUrl: "app/productForm.component.html",     viewProviders: [{ provide: VALUE_SERVICE, useValue: "Oranges" }] }) export class ProductFormComponent {     // ...statements omitted for brevity... }

The final change is to the productTable.component.ts file, as shown in Listing 21-21.

Listing 21-21 Updating the Import Reference in the productTable.component.ts File in the src/app Folder

import { Component, Input, ViewChildren, QueryList } from "@angular/core"; import { Model } from "./model/repository.model"; import { Product } from "./model/product.model"; import { DiscountService } from "./common/discount.service"; import { LogService } from "./common/log.service"; @Component({     selector: "paProductTable",     templateUrl: "app/productTable.component.html",     providers:[LogService] }) export class ProductTableComponent {     // ...statements omitted for brevity... }

Updating the Root Module

The final step is to update the root module so that it loads the common module to provide access to the directives and pipes it contains, as shown in Listing 21-22.

Listing 21-22 Importing a Feature Module in the app.module.ts File in the src/app Folder

import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { ProductComponent } from "./component"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { ProductTableComponent } from "./productTable.component"; import { ProductFormComponent } from "./productForm.component"; import { PaDiscountDisplayComponent } from "./discountDisplay.component"; import { PaDiscountEditorComponent } from "./discountEditor.component"; import { ModelModule } from "./model/model.module"; import { CommonModule } from "./common/common.module"; @NgModule({     imports: [BrowserModule, FormsModule, ReactiveFormsModule,         ModelModule, CommonModule],     declarations: [ProductComponent, ProductTableComponent,         ProductFormComponent, PaDiscountDisplayComponent, PaDiscountEditorComponent],     bootstrap: [ProductFormComponent, ProductTableComponent] }) export class AppModule { }

The root module has been substantially simplified with the creation of the common module, which has been added to the imports list. All of the individual classes for directives and pipes have been removed from the declarations list, and their associated import statements have been removed from the file. When common module is imported, all of the types listed in its exports property will be added to the root module’s declarations property.

Once you have saved the changes in this section, you can run the ng serve command to start the Angular development tools. Once again, there is no visible change in the content presented to the user, and the differences are all in the structure of the application.

Creating a Feature Module with Components

The final module that I am going to create will contain the application’s components. The process for creating the module is the same as in the previous examples, as described in the sections that follow.

Creating the Module Folder and Moving the Files

The module will be called components, and I created a src/app/components to contain the files. Run the following commands in the example folder to move the directive TypeScript, HTML, and CSS files into the new folder and to delete the corresponding JavaScript files:

mv src/app/*.component.ts src/app/components/ mv src/app/*.component.html src/app/components/ mv src/app/*.component.css src/app/components/

The result of these commands is that the component code files, templates, and style sheets are moved into the new folder, as listed in Table 21-7.

Table 21-7 The File Moves Required for the Component Module

Creating the Module Definition

To create the module, I added a file called components.module.ts to the src/app/components folder and added the statements shown in Listing 21-23.

Listing 21-23 The Contents of the components.module.ts File in the src/app/components Folder

import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { CommonModule } from "../common/common.module"; import { FormsModule, ReactiveFormsModule } from "@angular/forms" import { PaDiscountDisplayComponent } from "./discountDisplay.component"; import { PaDiscountEditorComponent } from "./discountEditor.component"; import { ProductFormComponent } from "./productForm.component"; import { ProductTableComponent } from "./productTable.component"; @NgModule({     imports: [BrowserModule, FormsModule, ReactiveFormsModule, CommonModule],     declarations: [PaDiscountDisplayComponent, PaDiscountEditorComponent,         ProductFormComponent, ProductTableComponent],     exports: [ProductFormComponent, ProductTableComponent] }) export class ComponentsModule { }

This module imports BrowserModule and CommonModule to ensure that the directives have access to the services and the declarable classes they require. It exports the ProductFormComponent and ProductTableComponent, which are the two components used in the root component’s bootstrap property. The other components are private to the module.

Updating the Other Classes

Moving the TypeScript files into the components folder requires some changes to the paths in the import statements. Listing 21-24 shows the change required for the discountDisplay.component.ts file.

Listing 21-24 Updating a Path in the discountDisplay.component.ts File in the src/app/component Folder

import { Component, Input } from "@angular/core"; import { DiscountService } from "../common/discount.service"; @Component({   selector: "paDiscountDisplay",   template: `<div class="bg-info text-white p-2">                 The discount is {{discounter.discount}}                </div>` }) export class PaDiscountDisplayComponent {   constructor(private discounter: DiscountService) { } }

Listing 21-25 shows the change required to the discountEditor.component.ts file.

Listing 21-25 Updating a Path in the discountEditor.component.ts File in the src/app/component Folder

import { Component, Input } from "@angular/core"; import { DiscountService } from "../common/discount.service"; @Component({   selector: "paDiscountEditor",   template: `<div class="form-group">                    <label>Discount</label>                    <input [(ngModel)]="discounter.discount"                         class="form-control" type="number" />                </div>` }) export class PaDiscountEditorComponent {   constructor(private discounter: DiscountService) { } }

Listing 21-26 shows the changes required for the productForm.component.ts file.

Listing 21-26 Updating a Path in the productForm.component.ts File in the src/app/component Folder

import {   Component, Output, EventEmitter, ViewEncapsulation,   Inject, SkipSelf } from "@angular/core"; import { Product } from "../model/product.model"; import { ProductFormGroup } from "../model/form.model"; import { Model } from "../model/repository.model"; import { VALUE_SERVICE } from "../common/valueDisplay.directive"; @Component({   selector: "paProductForm",   templateUrl: "productForm.component.html",   viewProviders: [{ provide: VALUE_SERVICE, useValue: "Oranges" }] }) export class ProductFormComponent {   form: ProductFormGroup = new ProductFormGroup();   newProduct: Product = new Product();   formSubmitted: boolean = false;   constructor(private model: Model,     @Inject(VALUE_SERVICE) @SkipSelf() private serviceValue: string) {     console.log("Service Value: " + serviceValue);   }   submitForm(form: any) {     this.formSubmitted = true;     if (form.valid) {       this.model.saveProduct(this.newProduct);       this.newProduct = new Product();       this.form.reset();       this.formSubmitted = false;     }   } }

Listing 21-27 shows the changes required to the productTable.component.ts file.

Listing 21-27 Updating a Path in the productTable.component.ts File in the src/app/component Folder

import { Component, Input } from "@angular/core"; import { Model } from "../model/repository.model"; import { Product } from "../model/product.model"; import { DiscountService } from "../common/discount.service"; import { LogService } from "../common/log.service"; @Component({   selector: "paProductTable",   templateUrl: "productTable.component.html",   providers:[LogService] }) export class ProductTableComponent {   constructor(private dataModel: Model) { }   getProduct(key: number): Product {     return this.dataModel.getProduct(key);   }   getProducts(): Product[] {     return this.dataModel.getProducts();   }   deleteProduct(key: number) {     this.dataModel.deleteProduct(key);   }   dateObject: Date = new Date(2020, 1, 20);   dateString: string = "2020-02-20T00:00:00.000Z";   dateNumber: number = 1582156800000; }

Updating the Root Module

The final step is to update the root module to remove the outdated references to the individual files and to import the new module, as shown in Listing 21-28.

Listing 21-28 Importing a Feature Module in the app.module.ts File in the src/app Folder

import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { ProductComponent } from "./component"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { ProductTableComponent } from "./components/productTable.component"; import { ProductFormComponent } from "./components/productForm.component"; import { ModelModule } from "./model/model.module"; import { CommonModule } from "./common/common.module"; import { ComponentsModule } from "./components/components.module"; @NgModule({     imports: [BrowserModule, FormsModule, ReactiveFormsModule,         ModelModule, CommonModule, ComponentsModule],     bootstrap: [ProductFormComponent, ProductTableComponent] }) export class AppModule { }

Adding modules to the application has radically simplified the root module and allows related features to be defined in self-contained blocks, which can be extended or modified in relative isolation from the rest of the application.

Summary

In this chapter, I described in the last of the Angular building blocks: modules. I explained the role of the root module and demonstrated how to create feature modules in order to add structure to an application. In the next part of the book, I describe the features that Angular provides to shape the building blocks into complex and responsive applications.