Got an Angular app that's in desperate need of routing? If so, consider using lazy-loaded routes.

Why? Because they offer the following advantages:

  • Faster rebuilds - You can check your recent changes more quickly.
  • Quicker start time - Who wants to wait for a while when the application starts?
  • Encapsulation - Your route works in isolation so changes you make in one route won't break other routes.

In this guide, I'll go over how you can add lazy-loaded routes to your Angular app.

As usual, you're free to check out the code on GitHub rather than read the article if that's how you roll.

Remember, though: if you're having problems running the Angular app locally, be sure to check the README.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub code for each guide belongs to a specific branch. The master branch holds the entire application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Note that the website displays the guides in reverse chronological order so if you want to start from the beginning, go to the last page.

The Business Requirements

Your boss Smithers walks into your office and tells you that it's time to get some routing going with the CRM app that you're working on.

Specifically, he'd like to see a home page and a user login page.

The user login page definitely makes sense, he says, because you've done such a great job with the user login work thus far. But as of yet it has no UI.

"That's about to change," he says as he leaves your office.

Routing in Angular

This is an excellent time to go over the concept of routing in Angular.

Remember, Angular apps are single-page apps. They're not like traditional blogs with new pages at every URL.

For some, that's hard to believe.

When you run an Angular app, you'll see different URLs in your browser's top bar as you navigate around. And those different URLs will display different content.

But under the covers, it's all one page.

That's why Angular apps, as a rule of thumb, ain't no good if you're into SEO.

Although Google says it can parse JavaScript and index content from a single-page app using distinct URLs, I wouldn't count on that. Go a different route (no pun intended) if you need SEO.

Architecting the App

Thus far, we haven't done much with architecting the CRM application. We've just modified the application component to use a couple of services.

But now it's time to look at the "big picture."

And since this article is about routing, it's also about architecting.

Why? Because routes in Angular take you to specific components. And you need to know how to lay out those components within your app.

You also need to decide where to create modules.

Of Modules and Components

Let's take a shallow dive into the concepts of modules and components in Angular.

Modules are groups of related components. They also include dependencies, such as services and directives.

Components, on the other hand, represent simple views. A component is a template and a stylesheet with some TypeScript code that includes business logic.

Angular groups related components into modules so that various segments of the app can run indepdently. It's the encapsulation thing again.

So how do you know when/where to create modules? I like to group them by what I call "large use cases."

What's a large use case? It's an overarching use case that encompasses several related use cases.

For example: user login, user account modification, and user profile image upload are all user-related use cases. So I'd group all user-related activities into one module.

And since this is a CRM app, it's also a good idea to group all contact-related activities into one module. That will happen in a future article, though.

Finally, the home page deserves its own module as well. Sure, it's relatively small at first, but it's a good idea to make the home page scalable.

Using the CLI

Throughout this guide, you haven't done a whole lot with the command-line interface (CLI) yet. But today you'll use it.

Open up a command prompt and navigate to the directory where you keep the source code for the CRM app. Then, enter this command:

ng g m home

That will create a home directory under src/app in your tree. It will also populate that directory with a module file called home.module.ts.

The above command, by the way, means "generate a module named home." The "g" means generate and the "m" means "module."

And with that, you've just created your first module (not including the app module). Congratulations!

Now create a comoonent for that module:

ng g c home

Once again, the "g" stands for generate. The "c" stands for component.

Now you've just created a home component to go along with your home module. That was easy!

Now create a module for user:

ng g m user

But you don't need a component for user. I'll go over that in more detail later on.

A Change to tsconfig.json

You won't often touch this part of the code but here it's necessary. Open up tsconfig.json in the root directory of your source tree.

Now look for this line:

"module": "es2015",

If you see that, change it to:

"module": "esnext",

Why? Because the esnext module supports dynamic importing.

And you're going to need dynamic importing if you want to do lazy-loaded routes. That is, in fact, how routes are lazily loaded in Angular apps.

The App Routing Module

Okay, I've teased this long enough. Now it's time to get busy with some routing.

Create a new file called app-routing.module.ts in the src/app directory. Then, populate it as follows:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';


const routes: Routes = [
    { path: '', pathMatch: 'full', redirectTo: 'home' },
    { path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) },
    { path: 'user', loadChildren: () => import('./user/user.module').then(m => m.UserModule) }
];

@NgModule({
    imports: [
        RouterModule.forRoot(routes)
    ],
    exports: [RouterModule],
    providers: []
})
export class AppRoutingModule { }

Pay attention to the routes constant. That's an array of top-level routes.

For starters, an empty ('') route redirects to home. That makes sense because usually you want to display the home page if the browser just accesses a website by its domain name and nothing else.

Both the home and user routes handle dynamic importing. That's why you see thse import statements. 

That dynamic import, by the way, also the lazily loads the route. Angular won't import the module until the user accesses that route.

But what about loadChildren? What does that mean?

Remember, these are top-level routes. That means there could be lower-level routes as well.

For example, it's not likely someone will access a URL that ends with just /user and nothing else.

Instead, that person will probably want to visit a URL like /user/login or /user/account-info.

Those secondary path segments (/login and /account-info) are routes themselves. So loadChildren loads other routes that are children of the top-level routes.

Now take a look at the @NgModule decorator. Specifically, pay attention to that imports line.

It's not often that you see an import line reference a static function. But that's exactly what's happening here.

Specifically, that forRoot() function creates a module with all the router providers and directives. It accepts the routes constant (an array of routes) that I just covered. Those are the routes that define navigation within the app.

By the way: some of the stuff you did manually here you can also automate at the command line. Just use the  --route and --module options when you create the module.

I'll probably go over those options in a future guide.

Updating the App Module

Now that you've created the routing module, you have to do something with it. Angular won't just figure out that it's there all by itself.

So what do you have to do? Just import it into your app module.

Open up app.module.ts in src/app and add the module you just created to imports. It should look like this:

  imports: [
      BrowserModule,
      HttpClientModule,
      AppRoutingModule
  ],

If you get an error when you add it like that, be sure to import the module at the top:

import { AppRoutingModule } from './app-routing.module';

Bingo. Save the file.

A Login Component

Now it's time to create a new component: a login component!

Back to the command line. Enter this command:

ng g c user/login

That creates a login component as part of the user module.

Unsurprisingly, if you look at your source tree, you'll see the component files under src/app/user/login.

Now, edit user.module.ts in /src/app/user and make it look like this:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginComponent } from './login/login.component';
import { RouterModule } from '@angular/router';

export const routes = [
    { path: '', redirectTo: 'login', pathMatch: 'full' },
    { path: 'login', component: LoginComponent }
];

@NgModule({
  declarations: [LoginComponent],
  imports: [
      CommonModule,
      RouterModule.forChild(routes)
  ]
})
export class UserModule { }

That routes constant looks pretty familiar, doesn't it? As in the previous section, it's an array of Route objects.

However, you're not seeing any dynamic importing here. Why is that?

Because the only route in that array takes you to a login page. It serves up a component (LoginComponent) because it represents a full URL endpoint.

Also, I'm making it easy to map URLs to components in the code. The /user/login path points to the /user/login directory in the Angular source tree (off of /src/app).

My vision is to follow this pattern throughout the development process.

Now as far as the routes themselves go, it's important to understand the context. The 'login' that you see above represents the /user/login URL path.

Where does that user part come from? It's specified as the top-level part of the URL in the routing module you saw previously. Go back and take a look at it again and you'll see it there.

So that means that the blank path (the first one you see in the array with '') refers to a simple /user path with nothing else. As you can see from that first object in the array, the app will forward that request to /user/login.

That /user/login path, by the way, references LoginComponent. So that's the component that will display the view and handle any logic associated with that path.

And if you look at the @NgModule decorator, this time you'll see the code is using the forChild() static method in RouterModule instead of forRoot().

Why? Because these aren't top-level routes. They're routes associated with submodules. 

So, yeah, they're "children."

Updating the Login Component

As I mentioned above, the /user/login URL path references the LoginComponent. So let's put some logic in there.

But first, let's move some services.

Remember, a module consists of related components and their services. So it's a good idea to keep user-specific services in the user module.

That's why you should move authentication.service.ts and user.service.ts from the /src/app/services directory to the /src/app/user directory.

You might see some errors after that move. Don't worry, you'll take care of them.

Next, edit login.component.ts in src/app/user/login and make it look like this:

import { Component, OnInit } from '@angular/core';
import { AuthenticationService } from '../authentication.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

    constructor(private authenticationService: AuthenticationService) {
        let user$ = authenticationService.login("darth", "thedarkside");

        user$.subscribe(
            (data: any) => console.log(data),
            err => console.error(err)
        );
    }

  ngOnInit() {
  }

}

If you've been following along in this series, that constructor will look very familiar to you. I just moved it from app.component.ts to login.component.ts.

That constructor just performs a login in the background and gets a JSON web token.

But speaking of app.component.ts....

Updating the App Component

Edit app.component.ts in /src/app. Make it look like this:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Carey Development CRM';
}

The import and the constructor are gone. Your errors should be gone, too.

Next, update the app.component.html file in the same directory. Make it look like this:

<router-outlet></router-outlet>

Wait... What? 

That's it?!? What the heck is that?

Yes, that's it.

But what is it?

It is the router outlet or place where Angular will display your views.

Remember: Angular creates single-page apps. So instead of switching pages around, the code will display different views within the <router-outlet> element

So when your users visit a specific URL within your app, Angular will find the component associated with that URL and render its view between those two tags.

Yep. That's it.

Updating the Home Module

Just one more thing before you can test. Update the home.module.ts file in src/app/home.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HomeComponent } from './home.component';
import { RouterModule } from '@angular/router';


export const routes = [
    { path: '', component: HomeComponent }
];

@NgModule({
  declarations: [HomeComponent],
  imports: [
      CommonModule,
      RouterModule.forChild(routes)
  ]
})
export class HomeModule { }

It looks very similar to the user module you edit previously, doesn't it? 

Testing It Out

And now it's time to test.

Save everything and fire up your User service Spring Boot application.

Now go back to the command line and enter:

ng serve

That launches your Angular app.

Now that you've got both apps up and running, head over to Google Chrome and open Developer tools in the right-hand sidebar.

Then, visit this URL: http://localhost:4200

If everything went right, the application should forward you to http://localhost:4200/home.

Why? Because that's exactly what you told it to do in the app-routing.module.ts file. Specifically, this line:

    { path: '', pathMatch: 'full', redirectTo: 'home' },

Now take a look at the screen. You should see "home works!"

But where did that come from? Angular created it when you generated the home component from the command line. It's a nice way to test new components.

In this case, it worked.

Not much to see in the console there, so take a look at user login. 

Visit this URL next: http://localhost:4200/user/login

In the window, you should see "login works!" That's a good sign.

But take a look at the console on the right-hand sidebar. You should see a few familiar lines indicating that your login worked:

And there you go. Success.

Wrapping It Up

Congratulations! You've got lazy-loading routes implemented in your Angular app.

Now, tinker a bit more. Add some new routes. Try using the --route option with the --module option when you create new modules.

Add a few components if you dare.

Take the time to learn more about routing in Angular. There are plenty of resources online that you can browse.

Have fun!

Photo by Diego Jimenez on Unsplash