In the last post we learned all about modules and how we can use feature modules to organize our code, but how do we connect them? That’s where the routing configuration comes in. Using this configuration we can define which URLs should load which modules and components. We’ll start by discussing the root module and work our way down from there.
Sample Project
Before we get started I think it will be easier to discuss this with a more concrete example. From now on each post will start with a link to the starting source code. Feel free to grab a copy and follow along as we’re going through it. I’ll still try and put enough detail in the post so you can understand the concepts if you’re not in a position to run the code.
The source code for this post can be found here: Getting Started with Angular: Routing
The project will be a simple e-commerce website. We’ll develop this as we go along and progressively add features with each post. To start with we’ve got:
- The root module with the bootstrapped app component
- A home component declared in the app module
- A feature module called
ProductList
to display, surprisingly, a list of products
At the moment everything is hard-coded, but in a future post, we’ll cover retrieving data using services and requests to an API. I have also included Angular Material, a component library built for Angular using the Material Design specification. This shouldn’t interfere with an understanding of the base Angular concepts but I will highlight and discuss any usages I think may confuse.
What is a Route?
Since Angular is a single-page application framework we’re not getting a new page whenever the URL changes. Instead, we configure the routes in our application so Angular knows which components are required. This is done by providing a pattern and an action. The pattern is checked against the current URL. If the pattern matches it will run the action, which involves either loading in a component or passing the processing on to another module.
Routing Module
If we go to our AppModule
we can see one of the things it imports is AppRoutingModule
. Opening the AppRoutingModule
we can see it’s split into 2 parts; the route configuration and the registration. I’ll start with the registration since there’s not a lot to cover:
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
The import call here is RouterModule.forRoot(routes)
. Adding this line is where the routes are being registered with the application. Having a separate module just for routing is another stylistic preference. We could just as easily put the RouterModule.forRoot
call directly in our root module and it would work the same.
Something to note here, root modules and features modules use a different method to provide their route configuration. As we’ve just seen the root module uses the forRoot
method. Feature modules must use the forChild
method. If you configure the routes in your feature modules using forRoot
you will get an error in the console when you attempt to load that module. This happens because forRoot
creates and provides a new Router service, of which there can be only one.
Now, on to our route configuration:
const routes: Routes = [
{
path: 'products',
loadChildren: () => ProductListModule
},
{
path: '',
component: HomeComponent
}
];
Here we can see we’re attempting to match the URL against 2 paths; “products” and an empty string. When setting up your paths you don’t need to use absolute paths, the matching only starts on the sections after your domain.
We’ll start at the bottom with the empty path. This is the path that will match against your base URL, so when running it locally this will be http://localhost:4200
. This has been configured to just use a component, in this case, the HomeComponent
. This component must be declared in the module these routes are being configured for since there’s no guarantee it will be loaded otherwise. There is an exception to this which we’ll discuss soon.
The other configuration is where we start getting feature modules involved. This will match the path http://localhost:4200/products
. The loadChildren
action accepts a callback that needs to return the module to be loaded. In the example above we directly return the ProductListModule
. If we run the app we will see that this runs and loads the page we expect. There is a problem with this approach though, the ProductListModule
is being eagerly loaded.
Lazy Loading
Eagerly loaded modules will be downloaded, along with the dependent components, when the parent route module is initiated. This can slow down the initial page load and causes unnecessary downloads for pages we may never use.
Since our sample application is so small this won’t cause a problem. What happens if we add another 100 components, directives, and pipes? Each with dependent services? Suddenly we’re downloading a lot more than we need to and our page load time has tanked. Ideally, we only want to download these components if and when they’re required. We can do this with lazy loading.
Thankfully, setting up lazy loading is easy to do in the routing configuration. It will even be automatically configured this way if generating modules using the Angular CLI. We just need to change:
loadChildren: () => ProductListModule
To:
loadChildren: () => import('./product-list/product-list.module').then(m => m.ProductListModule)
And the product is now being loaded only when we navigate to the product list page. Simple as that. It looks a little more daunting, but there’s nothing to it. Only 2 things are happening:
- We’re importing a file inline (
./product-list/product-list.module
) - Once we have this file we’re specifying what to load from it
Wildcard Matches
There’s another problem with our routing. What happens if we go to a page that doesn’t exist? If we navigate to http://localhost:4200/fake-page
we’re just presented with a blank page. If we open the devtools and look in the console we’ll see an error like this:
Error: NG04002: Cannot match any routes. URL Segment: 'fake-page'
Since we’ve not configured a route for fake-page
it doesn’t know where to direct the request. We can fix this by declaring a wildcard route. We’ll start by creating a new component. To do this we go back to the command line and run:
ng generate component page-not-found
This will create a new PageNotFoundComponent
and declare it in our root module. Now we’re going back to app-routing.module.ts
and adding a new entry to the end of our routes:
{
path: '**',
component: PageNotFoundComponent
}
Now when we go to http://localhost:4200/fake-page
we don’t get a blank page. We should get this:
It’s important to note here, the order of the routes matters. If we put the wildcard route at the start of our configuration we’ll only ever get the PageNotFoundComponent
loaded. When processing a URL Angular will stop at the first match and provide that. Keep that in mind when constructing your routes.
What else?
I’ll be honest, there are a lot more intricacies to the routing configuration than I realised when I started this post. In the interest of moving forward, I won’t be covering everything in this post. I will try and write an in-depth post about the options provided to us through the routing configuration in the future. There are a few parts that will be covered in later posts, including:
- Routing and lazy-loading of stand-alone components
- Child routes in feature modules
- Route guards and their configuration
We’re not there yet. We haven’t even got to the core of outputting content in Angular. We’re going to fix that in the next post where we’re going to be covering components.
Updated code can be found here: Getting Started with Angular: Routing Complete
Top comments (0)