ng-module

Introduction to NgModules – Components, Dependency Injection, and Testing

As you may know, Angular 2 is now at release candidate 6 - while Angular will release bug-fixes, there should be no more API changes before the full 2.0 release.

The biggest change in RC5 is the introduction of NgModules - it affects how every Angular app is bootstrapped and organized. I'll explain to you how NgModules works below, but first:

NgModules

NgModule is a way to organize your dependencies for 1. the compiler and 2. dependency injection.

I'm going to quickly explain why we need NgModules and how to work with them. In five minutes, you'll understand NgModules enough to put it in your own apps.

The context here is to think about the two roles of the compiler and dependency injection in Angular. Briefly, Angular needs to know what components define valid tags and where dependencies are coming from.

NgModule vs. JavaScript Modules

You might be asking, why do we need a new module system at all? Why can't we just use ES6/TypeScript modules?

The reason is, whereas using import will load code modules into JavaScript, the NgModule system is a way of organizing dependencies within the Angular framework. Specifically around what tags are compiled and what dependencies should be injected.

The Compiler and Components

For the compiler, when you have an Angular template that has custom tags you have to tell the compiler what tags are valid (and what functionality should be attached to them).

E.g. if we have this component:

We want the compiler to know that the following HTML should use our hello-world component (and that hello-world isn't some random invalid tag):

In Angular 1, the hello-world selector would have been registered globally which is convenient until your app grows and you start having naming conflicts. For instance, it's not hard to imagine two open-source projects that might use the same selector.

If you've been using Angular 2 since the earlier versions, you may remember that previous versions required that you specify a directives option in your @Component annotation. This was good in that it was less "magic" and removed the surface area for conflicts. The problem was it's a bit onerous to specify all directives necessary for all components.

Instead, using NgModules we can tell Angular what components are dependencies at a "module" level. More on this in a second.

Dependency Injection and Providers

Recall that Dependency Injection (DI) is an organized way to make dependencies available across our app. It's an improvement over simply importing code because we have a standardized way to share singletons, create factories, and override dependencies at testing time.

In earlier versions of Angular 2 we had to specify all things-that-would-be-injected (with providers) as an argument to the bootstrap function.

Note on terminology: a provider provides (creates, instantiates, etc.) the injectable (the thing you want). In Angular when you want to access an injectable you inject a dependency into a function and Angular's dependency injection framework will locate it and provide it to you.

Now with NgModule each provider is specified as part of a module.

NgModules 101

So now that we understand why we need NgModules how do we actually use it? Here's the simplest case:

In this case we're defining a class HelloWorldAppModule - this is going to be the entry point of our application. Starting with RC5, instead of bootstrapping our app with a component, we bootstrap a module with bootstrapModule, as you see here.

NgModules can import other modules as dependencies. We're going to be running this app in our browser and so we import BrowserModule.

We want to use the HelloWorld component in this app. Here's a key thing to keep in mind: Every component must be declared in some NgModule. Here we put HelloWorld into the declarations of this NgModule.

We say the HelloWorld component belongs to the HelloWorldAppModule - every component can belong to only one NgModule.

You'll often group multiple components together into one NgModule, much like you might use a namespace in a language like Java.

If you want to bootstrap this module (that is, use this module as the entry point for an application), then you provide a bootstrap key which specifies the component that will be used as the entry-point component for this module.

So in this case we're going to bootstrap the HelloWorld component as the root component. However, the bootstrap key is optional if you're creating a module that doesn't need to be the entry-point of an application.

Component Visibility

In order to use any component, the current NgModule has to know about it. For instance, say we wanted to use a user-greeting component in our hello-world component like this:

For any component to use another component it must be accessible via the NgModule system. There are two ways to make this happen:

  1. Either the user-greeting component is part of the same NgModule (e.g. HelloWorldAppModule) or
  2. The HelloWorldAppModule imports the module that the UserGreeting component is in.

Let's say we want to go the second route. Here's the implementation of our UserGreeting component along with the UserGreetingModule:

Notice here that we added a new key: exports. Think of exports as the list of public components for this NgModule. The implication here is that you can easily have private components by simply not listing them in exports.

If you forget to put your component in both declarations and exports (and then try to use it in another module via imports) it won't work. In order to use a component in another module via imports you must put your component in both places.

Now we can use this in our HelloWorld component by importing it into the HelloWorldAppModule like so:

Specifying Providers

Specifying providers of injectable things is done by adding them to the providers key of a NgModule.

For instance, say we have this simple service:

and we want to be able to inject it on a component like this:

To do this with NgModule is easy: we pass ApiService to the providers key of the module:

Passing the constant ApiService here is the shorthand version of using provide like this:

We're telling Angular that when the ApiService is to be injected, create and maintain a singleton instance of that class and pass it in the injection. There are lots of different ways to inject things, and we go over all of them in the book.

In order to use those providers from another module, you guessed it, you have to import that module.

Because the ApiDataComponent and ApiService are in the same NgModule the ApiDataComponent is able to inject the ApiService. If they were in different modules, then you would need to import the module containing ApiService into the ApiAppModule.

Testing

New in RC5 we have a TestBed library from @angular/core/testing. When you write your tests you have to configure an NgModule there. Thankfully, TestBed provides a configureTestingModule option which takes an NgModule configuration and applies to the components created in your test.

For instance, here's how we might configure our NgModule to use the above samples:

When using TestBed.configureTestingModule. You can also override your providers or put custom testing components in declarations etc. Again, we have complete examples of these in the sample code for the book.

NgModules Tips and Gotchas

The community conventions around NgModules are still being worked out. That said, my advice is to organize your NgModules like you organize your packages already. Group ideas together and then import the top-level modules into your app module at bootstrap time.

There are two "gotchas" I want you to keep in mind:

First, if you want to use a component in another module, make sure you add it to declarations and exports before you put it in imports (this bit me several times).

Second, you can find the common directives like NgIf, NgFor, etc. by import { CommonModule } from '@angular/common'. This happens by default when using BrowserModule but if you're writing tests you may not import BrowserModule and thus you'd get an error.

The advice generalizes to: make sure you understand the module dependency flow, especially when testing. Failure to import modules properly will result in failing tests (and the error messages aren't always clear).

Summary

NgModules is a great way to clearly specify which dependencies your components require. While it is slightly more ceremony up-front, the long-term effect is that we're able to organize dependencies explicitly and minimize conflicts - which is a requirement for larger apps.

Of course, there are a lot more details and configuration options for NgModules and so I'd encourage you to checkout the official documentation.

We also have over a dozen examples of using NgModule in the sample code of ng-book 2. If you want become an expert at NgModule and all of Angular 2, checkout ng-book 2