3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 7

NgModules, Routing, and Standalone Components

Why you need to understand modules and why you won't need them

STANDARD

NgModules, Routing, and Standalone Components

NOTE: This lesson is dedicated to explaining how to build Angular applications if you are using NgModules instead of standalone components. We will not be using them in this course, but it is still worthwhile to read through this lesson even if you don’t intend to use them at all. It will provide you with additional context, and there is a high chance you will run into Angular code that uses them at some point.

Now we are getting into the trickier side of things. Angular’s concept of modules, which are classes decorated with the @NgModule decorator (not to be confused with standard ES6 modules that we import and export at the top of files). NgModules can be hard to wrap your head around, so if you don’t already have experience with them, don’t expect to understand everything right away. You will learn as you run into walls and errors, and eventually, figure out the peculiarities of modules. When people say Angular has a steep learning curve, modules are probably a significant part of this. I hope this lesson will help to give you a head start, and also give you something to come back and reference.

We’ve already been exposed to this concept a little bit through the root module that we have discussed with in our example app:

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    SettingsComponent,
    WelcomeComponent,
    RandomColor,
    ReversePipe,
  ],
  imports: [BrowserModule, AppRoutingModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

By having all of these components, directives, and pipes declared within the same @NgModule, they will all be aware of each others existence and can use each other.

Our experience with this so far has mostly just been as a place to add the components, directives, and pipes we have been creating. We also discussed how this root AppModule is passed into the bootstrapModule method in the main.ts file:

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

Which is what kicks off our whole application. Since our root component AppComponent is supplied to the bootstrap property in the @NgModule it is available for us to use in the index.html file:

<body>
  <app-root></app-root>
</body>

So far, we just have the one module in our application, and everything just goes inside of that one module. This is not what we will typically do. Typically, we would have a module for each feature in our application. We keep using the example of a home page and a settings page. In that example application, we would likely have three modules:

  • AppModule
  • HomeModule
  • SettingsModule

This allows us to create some modularity in our application. The AppModule can include things that are needed for the application as a whole, the HomeModule can just include things relevant to the home feature, and the SettingsModule can just include things relevant to the settings feature.

This is useful from a code organisation perspective, but splitting our application up this way also allows us to lazy load parts of our application. We will discuss this in just a moment, but the general idea is that rather than loading all of the code for the entire application all at once, we could just load the code for the settings feature only when we try to access that feature.

NOTE: We still get lazy loading with standalone components without needing a module

In this lesson, we are going to discuss the anatomy of the @NgModule itself (because we haven’t talked about all of its common properties yet), and we are also going to discuss the general role of Angular modules. This will tie into a discussion around standalone components.

The Anatomy of @NgModule

We are not going to discuss every single feature of @NgModule, just the core concepts we will frequently be using. A useful exercise to do as we discuss this will be to refactor our home feature to use its own @NgModule instead of just dumping everything into the root module.

First, we can pull all of the components/pipes/directives we created out of the root AppModule:

app.module.ts

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

Now we can create a new module specifically for our home feature. We can create a separate file for this if we want to, e.g. home.module.ts, but we are going to just define this module in the same file as the HomeComponent itself:

import { CommonModule } from '@angular/common';
import { Component, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { RandomColor } from './ui/random-color.directive';
import { ReversePipe } from './ui/reverse.pipe';
import { WelcomeComponent } from './ui/welcome.component';

@Component({
  selector: 'app-home',
  template: `
    <app-welcome
      [name]="user.name"
      (cookiesAccepted)="handleCookies()"
    ></app-welcome>
    <p>I am the home component</p>
    <p randomColor>{{ 'reverse me ' | reverse }}</p>
  `,
})
export class HomeComponent {
  user = {
    name: 'Josh',
  };

  handleCookies() {
    console.log('do something');
  }
}

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
      {
        path: '',
        component: HomeComponent,
      },
    ]),
  ],
  declarations: [HomeComponent, WelcomeComponent, ReversePipe, RandomColor],
})
export class HomeModule {}

As we have discussed before, an @NgModule provides a compilation context for the things it contains. You can kind of imagine the @NgModule above like a box that contains our HomeComponent and everything else we are declaring or importing. Anything inside of this box is aware of the existence of all of the other things in the same box and can use them.

I’ve added the entire file here for context, but let’s focus on just the module:

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
      {
        path: '',
        component: HomeComponent,
      },
    ]),
  ],
  declarations: [HomeComponent, WelcomeComponent, ReversePipe, RandomColor],
})
export class HomeModule {}

We are now declaring everything we need for our home feature in its own HomeModule. The concept of the declarations is the same — we declare any component/directive/pipe we want to be able to use within this module.

What is new here is imports, or at least we haven’t talked about it yet.

NOTE: The imports array of an @NgModule is generally the same in concept as the imports array in standalone components.

In the context of an @NgModule an import is similar to a declaration, but instead of declaring individual components/pipes/directives we import an entire @NgModule. Anything that the @NgModule we are importing exports will be available within our HomeModule.

To go back to our box analogy, importing a module into another module is like taking the contents of one box (BoxA) and making them available to another box (BoxB). But, we don’t just dump the entire contents of BoxA into BoxB, we only supply BoxB with the items that were explicitly exported in BoxA with the exports property. We will see this in just a moment.

Both CommonModule and RouterModule are modules provided by Angular, and we are importing them into our own module. This means that we are getting access to the stuff inside of CommonModule and RouterModule, but only the things those modules export.

If you were to investigate what was inside CommonModule (this is a great exercise if you want to dig into that) you would find that it exports a bunch of directives and pipes including ngIf and ngFor. It is by importing the CommonModule into our HomeModule that we will be able to use *ngFor and *ngIf in our templates. If we want to use [(ngModel)] then we would need to import the FormsModule from Angular.

We can import modules provided by Angular into our own modules, and we can also import our own modules into our own modules to share functionality throughout the app. We will look at a more concrete example of this in the next section.

STANDARD
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).