Contents

Angular Modules

If you’re like the tens of thousands of Angular developers, you probably started learning Angular using a single app module. A lot of times that’s good enough for smaller applications. But there are other times when it makes sense to start splitting up your app into several modules. Here you’ll learn what Angular modules are, how they work, and when having multiple of them make sense.

The Basics

Angular has been using (and pushing!) the concept of modules ever since its inception back in the good old AngularJS days. Because they are required, each Angular application must have (so far) at least one Angular module. They are a difficult concept to grasp for beginners, and I’m happy to hear that with the introduction of Ivy, modules will be made optional eventually. But, before we get excited about a module-less future in Angular, let’s remember what Angular modules are for:

  • Serve as containers or boxes to help group together interrelated code logically.
  • Allow packaging code as reusable modules.
  • Maintain unit testing fast because (or only if) unnecessary code is not loaded.

Even if Angular modules become optional, they will remain a best-practice for enterprise applications for years to come.

If Angular modules are boxes, then each box can be organized internally into sections to arrange the types of things in it. An Angular module can have components, pipes, services, Angular module dependencies, Angular module exports, etc. Each type of thing must be arranged into its own category in order for Angular to understand the module declaration.

Let’s get into the weeds by learning how Angular modules are declared.

The @NgModule decorator

Angular heavily uses the TypeScript syntactic sugar called decorators (that’s the extra code that begins with an @ symbol) and @NgModule is not an exception. If the import or the export keywords make a TypeScript (or ES6) module, then the @NgModule decorator declares or defines an Angular module. In other words, the number of Angular modules in one Angular project will always equal the number of times you can search and find the @NgModule decorator in that same Angular project source code.

The @NgModule decorator must be called and an object must be passed in. This object can have the following keys: declarations, imports, providers, bootstrap. Each key, if included, must have an array as a value. This array typically has names of classes, which helps Angular categorize each class to its correct purpose.

Here’s what a typical Angular module would look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';

@NgModule({
  declarations: [ AppComponent, HelloComponent ],
  imports:      [ BrowserModule, FormsModule ],
  exports:      [ ],
  providers:    [ ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

Let’s look at each of the arrays (declarations, imports, providers, and bootstrap) in detail:

The bootstrap array

As mentioned before, every Angular app must have at least one Angular module. When it comes to components, we have the same rule: every Angular app must have at least one component which is called the root application component, or AppComponent by convention. How would Angular know which is the root component? It’d be convenient if it just looked for the AppComponent by class name, but that would be a run-time task that may take precious time that should be invested in actually loading the app. Instead, you must use the bootstrap array to inform Angular which component is your root application component. This component serves as the loading point for the entire application.

Keep in mind that every Angular application must have a root module, which must have the declarations array with at least one component in it. Because this defines our entry point to our application, the declarations array must be used in one Angular module (the root module) if the application has multiple modules.

The declarations array

The declarations array is where we can inform Angular of our components (other than the root app component), our directives, and our pipes. If we create a component and forget to include it in this array, Angular literally won’t know anything about it even though the code might be there.

There are some tips and tricks to keep in mind as we include components, directives, and pipes in this array:

  • If you add the same component to multiple declarations arrays within the same app, then Angular will complain about it. Any component, directive, or pipe must belong to only one Angular module.
  • If you attempt to add classes, services, or modules to the declarations array, Angular will complain about it too. Make sure you only add classes that are decorated as a component, directive, or pipe.
  • Any component, directive, and pipe declared in one Angular module can only be used within the same module by default. This is known as “private by default”. If you would like to turn this privacy for a component, directive, or pipe, simply include it as well in the exports array. More on that later.

The imports array

The imports array is extremely powerful. Just as in TypeScript you can make use of other classes and functions from a different file (or module), the imports in Angular array lets you do something similar with Angular modules. This allows you to extend the capabilities of your application by importing components, directives, and pipes that are exported from other Angular modules. This applies to your own custom modules, Angular modules such as HttpClientModule, etc., or third-party library modules. The imports array is the key to share Angular code with others and is heavily used by the Angular community.

Here are some explanations of the confusing parts of the imports array:

  • Importing a module only imports what is exposed by that module. Remember that everything in Angular modules is private by default, so only the things explicitly exported by the module can be used by another module that imports it. This not only applies to components, directives, and pipes but also to imported modules. In other words, if module A imports module B which in turn imports module C, module A won’t have access to module C through module B unless module B exports module C as well.
  • Another aspect to keep in mind is to only import what the module needs. With Ivy, this may not be such a big issue because of its ability to “shake the tree” and get rid of unnecessary imports at compile time. But it is still a good idea to keep the imports array as clean as possible and only import what is really needed.

The exports array

Would you like to share code (components, directives, pipes, or other modules’ components, directives, and pipes) in one Angular module with another module? Then the exports array is your friend. As you use the exports array, keep in mind the following:

  • When exporting a module, if the module itself doesn’t need it then you don’t have to import it in order to export it.
  • Never export a service. Services added to the providers array of an Angular module are registered with the root application injector, making them available for injection into any class in the application. Because of this, there’s no point in exporting them.

The providers array

The providers array was practically rendered useless after the introduction of the providedIn property of the @Injector decorator in Angular 6. However, it is still useful when trying to accomplish some edge use cases, which are beyond the scope of this article. Because you may encounter older code that still heavily uses the providers array, here’s a brief explanation and a few tips when working with this array:

  • This array was used to register all services before Angular 6.
  • Any service provider added to the providers array is registered at the foot of the application. This means that the service is available for injection in any component within the app.
  • The preferred way to register a service is to use the providedIn property of the @Injector decorator when available.

Having more than one module

The Angular CLI’s ng new command will start out with one root module and one routing module. This is a great start to begin your application as you may be doing a quick proof of concept or a small app. But as the app begins to grow beyond just a few components, it is highly recommended to start splitting up your application into what’s known as feature modules. Having your application split up this way (modularized) brings many benefits such as the ability to implement lazy loading and the promotion of good software architecture, among others.

Angular modules are very important to understand as Angular itself is modularized in this way. Hopefully, this gives you a better understanding of what Angular modules are and why they are important.