Putting Modules in Context
Question | Answer |
---|---|
What are they? | Modules provide configuration information to Angular. |
Why are they useful? | The root module describes the application to Angular, setting up essential features such as components and services. Feature modules are useful for adding structure to complex projects, which makes them easier to manage and maintain. |
How are they used? | Modules are classes to which the @NgModule decorator has been applied. The properties used by the decorator have different meanings for root and feature modules. |
Are there any pitfalls or limitations? | There is no module-wide scope for providers, which means that the providers defined by a feature module will be available as though they had been defined by the root module. |
Are there any alternatives? | Every application must have a root module, but the use of feature modules is entirely optional. However, if you don’t use feature modules, then the files in an application can become difficult to manage. |
Chapter Summary
Problem | Solution | Listing |
---|---|---|
Describing an application and the building blocks it contains | Use the root module | 1–7 |
Grouping related features together | Create a feature module | 8–28 |
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.
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-9. See Chapter 1 for how to get help if you have problems running the examples.
The Contents of the productTable.component.html File in the src/app Folder
The Contents of the productForm.component.html File in the src/app Folder
Understanding the Root Module
The Root Module in the app.module.ts File in the src/app Folder
The Angular Bootstrap in the main.ts File in the src Folder
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.
The @NgModule Decorator Root Module Properties
Name | Description |
---|---|
imports | This property specifies the Angular modules that are required to support the directives, components, and pipes in the application. |
declarations | This property is used to specify the directives, components, and pipes that are used in the application. |
providers | This property defines the service providers that will be used by the module’s injector. These are the providers that will be available throughout the application and used when no local provider for a service is available, as described in Chapter 20. |
bootstrap | This property specifies the root components for the application. |
Understanding the imports Property
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
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 processes 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.
The components listed in the bootstrap property must also be included in the declarations list.
The Root Component in the component.ts File in the src/app Folder
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.
Specifying Multiple Root Components in the app.module.ts File in the src/app Folder
Changing the Root Component Elements in the index.html File in the src Folder
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 Contents of the app.module.ts File in the src/app Folder
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.
The @NgModule Decorator Properties for Feature Modules
Name | Description |
---|---|
imports | This property is used to import the modules that are required by the classes in the modules. |
providers | This property is used to define the module’s providers. When the feature module is loaded, the set of providers is combined with those in the root module, which means that the feature module’s services are available throughout the application (and not just within the module). |
declarations | This property is used to specify the directives, components, and pipes in the module. This property must contain the classes that are used within the module and those that are exposed by the module to the rest of the application. |
exports | This property is used to define the public exports from the module. It contains some or all of the directives, components, and pipes from the declarations property and some or all of the modules from the imports property. |
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 File Moves Required for the Module
File | New Location |
---|---|
src/app/datasource.model.ts | src/app/model/datasource.model.ts |
src/app/form.model.ts | src/app/model/form.model.ts |
src/app/limit.formvalidator.ts | src/app/model/limit.formvalidator.ts |
src/app/product.model.ts | src/app/model/product.model.ts |
src/app/repository.model.ts | src/app/model/repository.model.ts |
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 Contents of the model.module.ts File in the src/app/model Folder
This purpose of a feature module is to selectively expose the contents of the 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.
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
Updating the Import Reference in the attr.directive.ts File in the src/app Folder
Updating the Import Reference in the categoryFilter.pipe.ts File in the src/app Folder
Updating Import Paths in the productForm.component.ts File in the src/app Folder
Updating Import Paths in the productTable.component.ts File in the src/app Folder
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.
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
Updating the Root Module in the app.module.ts File in the src/app Folder
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. (You may need to restart the Angular development tools and reload the browser to see the changes.)
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 blocks 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
The File Moves Required for the Module
File | New Location |
---|---|
app/addTax.pipe.ts | app/common/addTax.pipe.ts |
app/attr.directive.ts | app/common/attr.directive.ts |
app/categoryFilter.pipe.ts | app/common/categoryFilter.pipe.ts |
app/cellColor.directive.ts | app/common/cellColor.directive.ts |
app/cellColorSwitcher.directive.ts | app/common/cellColorSwitcher.directive.ts |
app/discount.pipe.ts | app/common/discount.pipe.ts |
app/discountAmount.directive.ts | app/common/discountAmount.directive.ts |
app/iterator.directive.ts | app/common/iterator.directive.ts |
app/structure.directive.ts | app/common/structure.directive.ts |
app/twoway.directive.ts | app/common/twoway.directive.ts |
app/valueDisplay.directive.ts | app/common/valueDisplay.directive.ts |
app/discount.service.ts | app/common/discount.service.ts |
app/log.service.ts | app/common/log.service.ts |
Updating the Classes in the New Module
Updating the Imports in the attr.directive.ts File in the src/app/common Folder
Updating the Imports in the categoryFilter.pipe.ts File in the src/app/common Folder
Creating the Module Definition
The Contents of the common.module.ts File in the src/app/common Folder
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
Updating the Import in the discountDisplay.component.ts File in the src/app Folder
Updating the Import Reference in the discountEditor.component.ts File in the src/app Folder
Updating the Import Reference in the productForm.component.ts File in the src/app Folder
Updating the Import Reference in the productTable.component.ts File in the src/app Folder
Updating the Root Module
Importing a Feature Module in the app.module.ts File in the src/app Folder
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 the 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 File Moves Required for the Component Module
File | New Location |
---|---|
src/app/app.component.ts | src/app/components/app.component.ts |
src/app/app.component.html | src/app/components/app.component.html |
src/app/app.component.css | src/app/components/app.component.css |
src/app/discountDisplay.component.ts | src/app/components/discountDisplay.component.ts |
src/app/discountEditor.component.ts | src/app/components/discountEditor.component.ts |
src/app/productForm.component.ts | src/app/components/productForm.component.ts |
src/app/productForm.component.html | src/app/components/productForm.component.html |
src/app/productForm.component.css | src/app/components/productForm.component.css |
src/app/productTable.component.ts | src/app/components/productTable.component.ts |
src/app/productTable.component.html | src/app/components/productTable.component.html |
src/app/productTable.component.css | src/app/components/productTable.component.css |
src/app/toggleView.component.ts | src/app/components/toggleView.component.ts |
src/app/toggleView.component.html | src/app/components/toggleView.component.ts |
Creating the Module Definition
The Contents of the components.module.ts File in the src/app/components Folder
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 components, 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
Updating a Path in the discountDisplay.component.ts File in the src/app/component Folder
Updating a Path in the discountEditor.component.ts File in the src/app/component Folder
Updating a Path in the productForm.component.ts File in the src/app/component Folder
Updating a Path in the productTable.component.ts File in the src/app/component Folder
Updating the Root Module
Importing a Feature Module in the app.module.ts File in the src/app Folder
Restart the Angular development tools to build and display the application. 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 the last of the Angular building blocks: modules. I explained the role of the root module and demonstrated how to create feature modules 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.