Angular Router Tutorial: Setting Up Routing in Your Application
In my previous post I discussed application structure using Angular modules, and set up the groundwork for using core, shared and feature modules. In this post I will demonstrate how to setup basic routing in an Angular application using Angular Router Modules.
**If you are following along with the tutorial, I have added bootstrap to the application. I have written about adding bootstrap to an angular application here: Using Bootstrap 4 with Angular
**If you are not following along, I currently have an generated application with a core module added in which we are going to place the rest of our application content.
Before we jump in, here are some links to other Angular Tutorials I’ve put together:
- Angular Tutorial: Getting started with the Angular CLI
- Angular Component Tutorial: Inputs, Outputs, and EventEmitters
- Angular Component Lifecycle
- Angular Module Tutorial: Application Structure Using Modules
- Using Bootstrap 4 with Angular
- Electron Tutorial: Getting started with Electron and Angular CLI
So, let’s get started with this Angular Router tutorial.
Generate Components
First we are going to need some additional components in the application. Let’s add a dashboard, sidebar and banner:
ng g component core/dashboard
ng g component core/sidebar
ng g component core/banner
These components should be added under the core/ folder and declared in our CoreModule
. Since these are root components for our CoreModule
we don’t need to create a feature module for them. Next we are going to set up our application routing, we will come back to these components shortly.
Create and Setup AppRoutingModule
The first thing we will have to do is add an AppRoutingModule
to define our root routes. In a previous post we had placed our core component selector in our app.component.html
. Instead of using the selector, we will setup routing and use routing to display our CoreComponent
in app.component.html
using a RouterOutlet
Angular.io documentation defines a RouterOutlet
as “a placeholder that Angular dynamically fills based on the current router state”. https://angular.io/api/router/RouterOutlet
To do so, do the following:
- If your are following along from previous posts, replace
<app-core></app-core>
with<router-outlet></router-outlet>
insideapp.component.html. Other
wise replace the content in app.component.html with <router-outlet></router-outlet>.
- Create a file called
app-routing.module.ts
at the same level as theAppComponent
- In the future, when you create a feature module that is going to have routing. Use a
--routing
flag with the generate command and this file will be created for you.
- In the future, when you create a feature module that is going to have routing. Use a
- Add the following code to
app-routing.modules.ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { CoreComponent } from './core/core.component'; const routes: Routes = [ { path: '', component: CoreComponent }, { path: '**', component: CoreComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
4. Add the new AppRoutingModule
to the imports array in AppModule
If you run ng serve
you will notice that the application is the same as when we had <app-core></app-core>
inside of app.component.html
. The difference now is that <router-outlet></router-outlet>
is dynamically selecting the CoreComponent
based on the state of the Angular router. The <router-outlet></router-outlet>
will check for a path corresponding to the routes provided to the RouterModule
, in this case routes
, and display the component that goes with that path.
In this case we have created two routes, one with the empty path and one with what is known as a wildcard path. Having CoreComponent
with the empty path means that when you navigate to localhost:4200
then CoreComponent
will be routed to. The wildcard path will match with any unrecognized paths. That means that any path we put after localhost:4200
will display the CoreComponent
. I would recommend adding some sort of path not found component and use that with the wildcard path but for now this will do.
RouterModule.forRoot(routes)
is providing our defined routes
to our application. All additional routes will have to be forChild
as I will demonstrate below.
Adding Child Routes
Next we are going to add our content to our CoreComponent
. This will demonstrate how child routes can be added and work with the <router-outlet></router-outlet>
. We want to add the banner and sidebar and then have a content area with another <router-outlet></router-outlet>
that our child route will navigate to. We will have it default to the DashboardComponent
. Add the following code:
core.component.html
<div class="container-fluid"> <div class="row"> <div class="col-12"> <app-banner></app-banner> </div> <div class="col-2 sidebar"> <app-sidebar></app-sidebar> </div> <div class="col"> <router-outlet></router-outlet> </div> </div> </div>
banner.component.html
<nav class="navbar navbar-dark navbar-expand"> <a class="navbar-brand">Angular.io Demo</a> </nav>
sidebar.component.html
<div class="d-flex flex-column"> <div class="p-2"> Home </div> <div class="p-2"> Routing Example </div> </div>
I have added some basic bootstrap and styling classes to layout my components. As you can see, in our CoreComponent
we added another <router-outlet></router-outlet>
tag. We will have to add child route definitions for the CoreComponent
route we previously defined. To do so we will create a new CoreRoutingModule
in the core/
folder. As I stated earlier, using the --routing
flag when generating the module will also generate the RoutingModule
.
- Create a
coure-routing.module.ts
file in your core/ folder. - Add the following code
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { DashboardComponent } from './dashboard/dashboard.component'; import { CoreComponent } from './core.component'; const coreRoutes: Routes = [ { path: '', component: CoreComponent, children: [ { path: '', component: DashboardComponent }, { path: 'dashboard', component: DashboardComponent } ] } ]; @NgModule({ imports: [RouterModule.forChild(coreRoutes)], exports: [RouterModule] }) export class CoreRoutingModule { }
3. Add CoreRoutingModule
to the imports array of the CoreModule
4. Remove the empty path to the CoreComponent
in the AppRoutingModule
What we are doing now, is declaring the empty path route to CoreComponent
in this module, and then giving child routes to that path. So now when we navigate to localhost:4200
we will get the CoreComponent
and then in the <router-outlet></router-outlet>
in core.component.html
it will dynamically select our DashboardComponent
. The second path for ‘dashboard’ means that we will get the same thing if we navigate to localhost:4200/dashboard
.
I should point out that the child routes are declared for the empty path route to CoreComponent
. Since we have the wildcard route used to get to CoreComponent
as well, it should be noted that the child routes will not work with the wildcard route. Basically localhost:4200/somewildcard/dashboard
is not going to display the same thing as localhost:4200/dashboard
even though the wildcard navigates to the CoreComponent
.
Depth First Search
A quick note on the functionality of the Angular router. When the Angular router looks through the applications defined routes for a corresponding route definition, it performs a depth first search. For a quick demonstration of this, try the following.
Update your coreRoutes array to look like this
const coreRoutes: Routes = [ { path: '', component: CoreComponent, children: [ { path: '', component: DashboardComponent }, { path: 'dashboard', component: DashboardComponent } ] }, { path: 'dashboard', component: DashboardComponent } ];
If you navigate to localhost:4200/dashboard
, the RouterOutlet
in AppComponent
will match the empty string first then search it’s children for ‘dashboard’ to go with the second RouterOutlet
. It will find it and display the CoreComponent
with the DashboardComponent
in the router-outlet for CoreComponent
. The second route to ‘dashboard’ here can never be found.
But if you change the coreRoutes array to look like this
const coreRoutes: Routes = [ { path: 'dashboard', component: DashboardComponent }, { path: '', component: CoreComponent, children: [ { path: '', component: DashboardComponent }, { path: 'dashboard', component: DashboardComponent } ] } ];
The Angular router in our AppComponent
will see the ‘dashboard’ path before the empty path and just display the DashboardComponent
without the CoreComponent
.
pathMatch
To avoid confusion with the empty string as demonstrated above. I encourage you to use the pathMatch
option for your routes.
{ path: '', pathMatch: 'full', redirectTo: 'dashboard' }, { path: 'dashboard', component: DashboardComponent }
Now when we navigate to localhost:4200
the router-outlet in AppComponent
will dynamically select CoreComponent
and the router-outlet in CoreComponent
will see the empty path, and redirect to localhost:4200/dashboard
routerLink Example
Assuming you will want to click around your application to navigate, you are going to need to understand the routerLink
directive. Here’s a quick example to get you started.
Since I want to treat my examples like features, I am going to create a RoutingExampleModule
and a RoutingExampleComponent
.
ng g module core/routing-example --routing
- Remember the routing flag will provide a routing module for future use.
ng g component core/routing
- add the
RoutingExampleModule
to the imports array inCoreModule
- Update your
coreRoutes
array inCoreRoutingModule
- You will probably have to remove the DFS example stuff.
const coreRoutes: Routes = [ { path: '', component: CoreComponent, children: [ { path: '', pathMatch: 'full', redirectTo: 'dashboard' }, { path: 'dashboard', component: DashboardComponent }, { path: 'routing-example', component: RoutingExampleComponent } ] } ];
- Update
sidebar.component.html
to use a routerLink directive to route to our components.
<div class="d-flex flex-column"> <div class="p-2"> <a routerLink="/dashboard" routerLinkActive="active">Home</a> </div> <div class="p-2"> <a routerLink="/routing-example" routerLinkActive="active">Routing Example</a> </div> </div>
Now when you run the application, clicking the links in the sidebar will change the content displayed by the <router-outlet></router-outlet>
in core.component.html
.
** routerLinkActive="active"
will set the class “active” on the element when the Angular router matches that link. Any class can be set using this method: routerLinkActive="someClassForActive"
Summary
With that you should have all the tools you need to get started with application routing in Angular. In a future blog I will demonstrate additional functionality for the Angular router in the RoutingExample section, but for now you should be able to get started with basic routing.