Loading...
a major part of any web application is how we deal with routing. Change in the route should display a different screen for the user or a part of the current screen should change. When user reloads the page, the app should return him to the exact view based on the url he is in.
When creating a Single page application, the routing should happend with HTML5 history push state, meaning the browser should not perform a reload when doing the navigation from our angular app, and of course although we are using the history push state to navigate we still have to support the back button as well.
This cause the navigation to be really quick. Dealling properly with urls has a major impact on Search Engine Optimization (SEO) as well, so there are certain rules we have to follow.
@angular/router package contains the module that contains the router and routing logic. You will need to install it with npm
> npm install @angular/router --save
If you start an angular cli project this package is already installed.
If we want to use router module services or directive we have to include it in the imports array, we will also need to consider now if our module contains routing.
Since we are breaking our angular app to modules, one root module and multiple feature modules, we will have to break our app routing to modules as well.
For example say we want to add a setting screens in our app, we have a user setting, account setting, and dashboard settings.
We decided our settings url to look like this:
create a new module containing the routing for our root module
> ng g module AppRouting
Also let's create a component for the homepage and a component for the about page
> ng g c HomePage
> ng g c AboutPage
Let's define our root module routing in the app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HomePageComponent } from '../home-page/home-page.component';
import { AboutPageComponent } from '../about-page/about-page.component';
@NgModule({
imports: [
RouterModule.forRoot([
{ path: '', component: HomePageComponent},
{ path: 'about', component: AboutPageComponent},
])
],
exports: [RouterModule]
})
export class AppRoutingModule { }
A few things to note here
...
imports: [
BrowserModule,
AppRoutingModule
],
...
Our routing for the app module is installed, but where will the router place the components after a route match?
The router will place the matched component to path inside the router outlet directive. Modify the root component template app.component.html to contain the router outlet directive.
<nav>
<h1>
This will be shared in all the routes
</h1>
</nav>
<router-outlet></router-outlet>
So the router outlet directive is a placeholder that tells the router where to place the matched component.
a router outlet can also be associated with a name, there can be more than a single router outlet. How exactly does it work with multiple router outlets.
we can place attribute in the router oulet directive called name, there can be only one unnamed router outlet which is called the primary outlet, and all the rest have to be named.
Let's create another router outlet named messages whose goal is to display a message. In the app.component.html add the following at the bottom of the page
<router-outlet name="message"></router-outlet>
Create a new component which goal is to display a message to the user
> ng g c Message
now modify the app-routing.module.ts to contain our new route in the routes array
...
{ path: 'message', component: MessageComponent, outlet: 'secondery'},
...
Let's add a link in the app component that will pop the message in the outlet.
modify the app.component.html
<nav>
<h1>
This will be shared in all the routes
</h1>
<ul>
<li>
<a [routerLink]="[{outlets: {secondery: ['message']}}]">
pop message
</a>
</li>
</ul>
</nav>
<router-outlet></router-outlet>
<router-outlet name="secondery"></router-outlet>
So to link to the named outlet we have to place a url in a specific outlet, so we place a dictionary with outlets key and the name of the outlet and the url of the navigation parts for that outlet.
Another awesome feature of angular routing is the built in lazy loading. Let's create our settings module which contain routes for user, dashboard, and account settings.
We know that our users won't access the settings module every time they activate the app, in fact most of them will just visit the settings once. For this reason we don't want to load all the code for the settings module every time the user loads our app, we want to load the settings module only when the user wants to access the setting pages.
Let's create our settings module, settings-routing module, account component, dashboard component, and user component that are part of the settings module.
> ng g module Settings --project routing-tutorial
> cd projects/routing-tutorial/src/app/settings/
> ng g module SettingsRouting
> ng g c Account
> ng g c User
> ng g c Dashboard
Modify the SettingsRouting module to contain the routes for the settings module.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { UserComponent } from '../user/user.component';
import { AccountComponent } from '../account/account.component';
import { DashboardComponent } from '../dashboard/dashboard.component';
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{path: 'user', component: UserComponent},
{path: 'account', component: AccountComponent},
{path: 'dashboard', component: DashboardComponent},
])
],
exports: [RouterModule]
})
export class SettingsRoutingModule { }
Notice that for this module we created the routes using the forChild method, so every feature module we are creating routes for will have the routing defined with forChild
notice that although we plan the user settings url to be /settings/user we still only placed in path the user and removed the common prefix from all the routes here.
We have to also modify the SettingsModule to include the settings routings module we just created.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AccountComponent } from './account/account.component';
import { UserComponent } from './user/user.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { SettingsRoutingModule } from './settings-routing/settings-routing.module';
@NgModule({
imports: [
CommonModule,
SettingsRoutingModule
],
declarations: [AccountComponent, UserComponent, DashboardComponent]
})
export class SettingsModule { }
Now in the root routing of our app we need to tell the router that when we are grabbing a url with prefix /settings load the settings module and the routing there will take control.
Modify the app-routing.module.ts to look like this:
{ path: 'settings', loadChildren: '../settings/settings.module#SettingsModule'}
Notice that the loadChildren is refrencing the file of the lazy loaded module as well as the name of the class.
Try and restart the ng serve, you should see a new file for the settings module is created, let's add a link in the app.component.html modify it to look like this:
<li>
<a routerLink="/settings/user">
user settings
</a>
</li>
<li>
<a routerLink="/settings/account">
account settings
</a>
</li>
<li>
<a routerLink="/settings/dashboard">
dashboard settings
</a>
</li>
try and click any of the new links and notice how the new created files for the settings module is loaded only when you are pressing the links
The last topic we want to cover is passing data from the url.
We can pass data with the url in two ways:
Let's create another module called TodoModule with a todo routing module and a component page for a list of todos and a single todo details page
> ng g module Todo --project routing-tutorial
> cd projects/routing-tutorial/src/app/todo
> ng g module TodoRouting
> ng g c TodoList
> ng g c TodoDetails
Modify the todo routing module and add routes for the two components. todo-routing.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { TodoListComponent } from '../todo-list/todo-list.component';
import { TodoDetailsComponent } from '../todo-details/todo-details.component';
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{path: '', component: TodoListComponent},
{path: ':id', component: TodoDetailsComponent},
])
],
exports: [RouterModule]
})
export class TodoRoutingModule { }
Notice that for the details route we are specifying that we are getting a matrix param called id.
In our todo details component we will want to access our param. Modify the todo-details.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { map, mergeMap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-todo-details',
templateUrl: './todo-details.component.html',
styleUrls: ['./todo-details.component.css']
})
export class TodoDetailsComponent implements OnInit {
todoItem$: Observable<any>;
constructor(private _activatedRoute: ActivatedRoute, private _httpClient: HttpClient) { }
ngOnInit() {
this.todoItem$ = this._activatedRoute.paramMap.pipe(
map((params: ParamMap) => params.get('id')),
mergeMap((id: string) => this._httpClient.get('https://nztodo.herokuapp.com/api/task/' + id + '/?format=json'))
)
}
}
Few things to note here, we are injecting the ActivatedRoute which contains detail about the current state of the router and the current match that the router found, among the data is the query params and matrix params the route contains.
We are getting the params from the activated route in an observable paramMap we do some rxjs operators magic to transform the data we get to an ajax call to the todo resource based on the id we are getting in the params.
We still need to connect the final todo routing to the todo module and also include in the imports the http client module. We will leave that for you folks back at home.
So in this article we played a bit with angular routing, we save how we can create modular routing, lazy load our modules, create multiple router-outlet and pass data with the url.