Routing
If we want, we could make an entire application without using routing and without changing the path of the URL at all.
So why would we be using routing then? Well for one, we can allow our users to immediately go to a certain part of our application. For example an admin, that could just go to the admin section of the application by using the /admin path.
Routing also provides us a way to pass parameters to other components through the URL.
Let's see how we can implement simple routing in an application in a simple demo you can find right here.
Basics
For our router to work, we have to indicate where we want our router output. For this, we can use the router outlet element, you can see this in the app.component.html.
app.component.html
<h2>{{ name }}</h2>
<router-outlet></router-outlet>
Now we can implement our routing. In our app.routing.ts we can find the top-level routing.
app.routing.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { IndexComponent } from './index.component';
import { SupportComponent } from './support.component';
const routes: Routes = [
{ path: '', redirectTo: '/index', pathMatch: 'full' },
{ path: 'index', component: IndexComponent },
{ path: 'support', component: SupportComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule]
})
export class AppRoutingModule { }
So there are multiple things going on.
As you can see, we can define a path, for example 'index', and the component that is associated with this path, IndexComponent. So the template of the index component will be placed inside of the router-outlet when we navigate to the /index route.
To redirect routing, we can use redirectTo. In this case, we will redirect path '', so an empty path, to /index.
For our redirect to work properly here, we have to use pathMatch: 'full'. This means that the path needs to match exactly and the routing gets completely resolved. This in contrary to <strongp<athMatch: 'prefix', here only the prefix needs to match, and the remaining path gets matched to child routes. As we are redirecting '', or basically an empty string, all our routes would match with the redirect, so to avoid this, we can use pathmatch: 'full'.
In the imports section we define the following: RouterModule.forRoot(routes, { useHash: true }). So what is this useHash and why do we use it?
By default, Angular routing uses PathLocationStrategy. When the URL is changed, thanks to html5 pushstate, the browser will not send a request to the server with the new URL and we can do the routing of our application client side. The problem here is when a user reloads the page, the browser will send the request again to the server.
So as long as the server side can work with this, there is no problem, but otherwise (I AM LOOKING AT YOU IIS!) we can switch to HashLocationStrategy by using useHash: 'true' at the root import of our routing.
This will add a # to our routing part, as the hashtag is normally used to indicate a position in a webpage to scroll to when opening, this is by default not passed to the server. And like that, we don't need the server side to be able to handle it, as it will not be sent anyway.
How to navigate
If you take a look in the index component, you can see that we can navigate to a route by using the navigate function of the router. For example: this.router.navigate(['/categories'])
Alternatively, we can also directly navigate in our template. Take for example in our categories overview component. In our template, we can see [routerLink]="['/categories/new']". Which navigates to the categories route and the child route /new.
Inception
We'll see how this child routing inside of routing works.
In our categories component, we can see in the template that there is a title and another router-outlet element. So when we use our categories routing, the title will be shown, and the output of our child routing will be in the nested router-outlet element.
categories.component.ts
import { Component } from '@angular/core';
@Component({ template:
`
<h2 style="text-align:center;">Categories</h2>
<router-outlet></router-outlet>
` })
export class CategoriesComponent { }
We also defined another routing module which we import in our categories module, and this adds the routing for our categories module.
categories.routing.ts
import {CategoriesComponent} from './categories.component';
import {OverviewComponent} from './overview.component';
import {NewCategoryComponent} from './new-category.component';
import { CategoryDetailComponent} from './category-detail.component';
import { RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';
const categoriesRoutes: Routes = [
{
path: 'categories', component: CategoriesComponent, children: [
{ path: '', component: OverviewComponent },
{ path: 'new', component: NewCategoryComponent},
{ path: ':id', component: CategoryDetailComponent },
]
},
];
@NgModule({
imports: [ RouterModule.forChild(categoriesRoutes) ],
exports: [ RouterModule ]
})
export class CategoriesRoutingModule { }
Now when we go to our the categories path, our top-level router-outlet element will contain our CategoriesComponent.
If we don't provide a subpath for our categories, the empty path will make sure that the nested routing will contain our OverviewComponent, which shows a list of all our categories.
When we do provide the subpath new, so our URL ends with /categories/new, the child routing will go to our NewCategoryComponent, where we can create a new category.
The syntax to provide a parameter is ':parameter', in this example we use ':id', which lets us use a path like for example /categories/3.
The order in which you specify the child routing is important. If you would put the ':id' route before the 'new' route, the last one would never be called as categories/new would also match the ':id' route and it would be interpreted as a parameter.
Parameters
We can easily pass along parameters in our route, but how can we use them in our components?
category-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { Category } from './category';
import { Router, ActivatedRoute } from '@angular/router';
import { CategoryService } from './category.service';
@Component({ template:
`
<h3>Category Detail</h3>
<p>
Id:
{{category.id}}
</p>
<p>
Name:
{{category.name}}
</p>
<p><a [routerLink]="['/categories/']">Back to overview</a>
` })
export class CategoryDetailComponent implements OnInit {
category:Category;
constructor (
private router:Router,
private route:ActivatedRoute,
private categoryService:CategoryService) {
}
ngOnInit(): void {
this.route
.paramMap
.subscribe(params => {
let id = +params.get('id');
this.category = this.categoryService.getCategory(id);
});
}
}
We can import the ActivatedRoute from @angular/router and access our parameters in the paramMap.
Pay attention that the paramMap returns an observable, so we have to subscribe to it to get the results back.
I'm routing for you
You can find the demo right here. If you have any troubles, you can always let me know in the comments below.