Milestone 1: Getting started with the router
Begin with a simple version of the app that navigates between two empty views.
Set the
The router uses the browser's history.pushState for navigation. Thanks to
pushState
, you can make in-app URL paths look the way you want them to look, e.g. localhost:3000/crisis-center
. The in-app URLs can be indistinguishable from server URLs.
Modern HTML5 browsers were the first to support
pushState
which is why many people refer to these URLs as "HTML5 style" URLs.
HTML5 style navigation is the router default. In the LocationStrategy and browser URL stylesAppendix, learn why HTML5 style is preferred, how to adjust its behavior, and how to switch to the older hash (#) style, if necessary.
You must add a element to the app's
index.html
for pushState
routing to work. The browser uses the
value to prefix relative URLs when referencing CSS files, scripts, and images.
Add the
element just after the
tag. If the app
folder is the application root, as it is for this application, set the href
value in index.html
exactly as shown here. href="/">
A live coding environment like Stackblitz sets the application base address dynamically so you can't specify a fixed address. That's why the example code replaces the
with a script that writes the
tag on the fly.document.write(' + document.location + '" />');
You only need this trick for the live example, not production code.
Importing from the router library
Begin by importing some symbols from the router library. The Router is in its own
@angular/router
package. It's not part of the Angular core. The router is an optional service because not all applications need routing and, depending on your requirements, you may need a different routing library.
You teach the router how to navigate by configuring it with routes.
Define routes
A router must be configured with a list of route definitions.
The first configuration defines an array of two routes with simple paths leading to the
CrisisListComponent
and HeroListComponent
.
Each definition translates to a Route object which has two things: a
path
, the URL path segment for this route; and a component
, the component associated with this route.
The router draws upon its registry of definitions when the browser URL changes or when application code tells the router to navigate along a route path.
In simpler terms, you might say this of the first route:
- When the browser's location URL changes to match the path segment
/crisis-center
, then the router activates an instance of theCrisisListComponent
and displays its view. - When the application requests navigation to the path
/crisis-center
, the router activates an instance ofCrisisListComponent
, displays its view, and updates the browser's address location and history with the URL for that path.
Here is the first configuration. Pass the array of routes,
appRoutes
, to the RouterModule.forRoot
method. It returns a module, containing the configured Router
service provider, plus other providers that the routing library requires. Once the application is bootstrapped, the Router
performs the initial navigation based on the current browser URL.import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { CrisisListComponent } from './crisis-list.component';
import { HeroListComponent } from './hero-list.component';
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component: HeroListComponent },
];
@NgModule({
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot(
appRoutes,
{ enableTracing: true } // <-- debugging="" only="" purposes="" span="">
)
],
declarations: [
AppComponent,
HeroListComponent,
CrisisListComponent,
],
bootstrap: [ AppComponent ]
})
export class AppModule { }-->
Adding the configured
RouterModule
to the AppModule
is sufficient for simple route configurations. As the application grows, you'll want to refactor the routing configuration into a separate file and create a Routing Module, a special type of Service Module
dedicated to the purpose of routing in feature modules.
Providing the
RouterModule
in the AppModule
makes the Router available everywhere in the application.The AppComponent shell
The root
AppComponent
is the application shell. It has a title, a navigation bar with two links, and a router outlet where the router swaps views on and off the page. Here's what you get:
The corresponding component template looks like this:
template: `
Angular
Router
RouterOutlet
The
RouterOutlet
is a directive from the router library that marks the spot in the template where the router should display the views for that outlet.
The router adds the
<router-outlet>
element to the DOM and subsequently inserts the navigated view element immediately after the <router-outlet>
.RouterLink binding
Above the outlet, within the anchor tags, you see attribute bindings to the
RouterLink
directive that look like routerLink="..."
.
The links in this example each have a string path, the path of a route that you configured earlier. There are no route parameters yet.
You can also add more contextual information to the
RouterLink
by providing query string parameters or a URL fragment for jumping to different areas on the page. Query string parameters are provided through the [queryParams]
binding which takes an object (e.g. { name: 'value' }
), while the URL fragment takes a single value bound to the [fragment]
input binding.
Learn about the how you can also use the link parameters array in the appendix below.
RouterLinkActive binding
On each anchor tag, you also see property bindings to the
RouterLinkActive
directive that look like routerLinkActive="..."
.
The template expression to the right of the equals (=) contains a space-delimited string of CSS classes that the Router will add when this link is active (and remove when the link is inactive). You can also set the
RouterLinkActive
directive to a string of classes such as [routerLinkActive]="'active fluffy'"
or bind it to a component property that returns such a string.
The
RouterLinkActive
directive toggles css classes for active RouterLink
s based on the current RouterState
. This cascades down through each level of the route tree, so parent and child router links can be active at the same time. To override this behavior, you can bind to the [routerLinkActiveOptions]
input binding with the { exact: true }
expression. By using { exact: true }
, a given RouterLink
will only be active if its URL is an exact match to the current URL.Router directives
RouterLink
, RouterLinkActive
and RouterOutlet
are directives provided by the Angular RouterModule
package. They are readily available for you to use in the template.
The current state of
app.component.ts
looks like this:import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
Angular
Router
a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
<router-outlet></router-outlet>
`
})
export class AppComponent { }Wildcard route
You've created two routes in the app so far, one to
/crisis-center
and the other to /heroes
. Any other URL causes the router to throw an error and crash the app.
Add a wildcard route to intercept invalid URLs and handle them gracefully. A wildcard route has a path consisting of two asterisks. It matches every URL. The router will select this route if it can't match a route earlier in the configuration. A wildcard route can navigate to a custom "404 Not Found" component or redirect to an existing route.
The router selects the route with a first match wins strategy. Wildcard routes are the least specific routes in the route configuration. Be sure it is the last route in the configuration.
To test this feature, add a button with a
RouterLink
to the HeroListComponent
template and set the link to "/sidekicks"
.import { Component } from '@angular/core';
@Component({
template: `
HEROES
Get your heroes here
`
})
export class HeroListComponent { }
The application will fail if the user clicks that button because you haven't defined a
"/sidekicks"
route yet.
Instead of adding the
"/sidekicks"
route, define a wildcard
route instead and have it navigate to a simple PageNotFoundComponent
.{ path: '**', component: PageNotFoundComponent }
Create the
PageNotFoundComponent
to display when users visit invalid URLs.import { Component } from '@angular/core';
@Component({
template: '
Page not found
'
})
export class PageNotFoundComponent {}
As with the other components, add the
PageNotFoundComponent
to the AppModule
declarations.
Now when the user visits
/sidekicks
, or any other invalid URL, the browser displays "Page not found". The browser address bar continues to point to the invalid URL.The default route to heroes
When the application launches, the initial URL in the browser bar is something like:
localhost:3000
That doesn't match any of the concrete configured routes which means the router falls through to the wildcard route and displays the
PageNotFoundComponent
.
The application needs a default route to a valid page. The default page for this app is the list of heroes. The app should navigate there as if the user clicked the "Heroes" link or pasted
localhost:3000/heroes
into the address bar.Redirecting routes
The preferred solution is to add a
redirect
route that translates the initial relative URL (''
) to the desired default path (/heroes
). The browser address bar shows .../heroes
as if you'd navigated there directly.
Add the default route somewhere above the wildcard route. It's just above the wildcard route in the following excerpt showing the complete
appRoutes
for this milestone.const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component: HeroListComponent },
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];
A redirect route requires a
pathMatch
property to tell the router how to match a URL to the path of a route. The router throws an error if you don't. In this app, the router should select the route to the HeroListComponent
only when the entire URL matches ''
, so set the pathMatch
value to 'full'
.
Technically,
pathMatch = 'full'
results in a route hit when the remaining, unmatched segments of the URL match ''
. In this example, the redirect is in a top level route so the remaining URL and the entire URL are the same thing.
The other possible
pathMatch
value is 'prefix'
which tells the router to match the redirect route when the remaining URL begins with the redirect route's prefix path.
Don't do that here. If the
pathMatch
value were 'prefix'
, every URL would match ''
.
Try setting it to
'prefix'
then click the Go to sidekicks
button. Remember that's a bad URL and you should see the "Page not found" page. Instead, you're still on the "Heroes" page. Enter a bad URL in the browser address bar. You're instantly re-routed to /heroes
. Every URL, good or bad, that falls through to this route definition will be a match.
The default route should redirect to the
HeroListComponent
only when the entire url is ''
. Remember to restore the redirect to pathMatch = 'full'
.
Learn more in Victor Savkin's post on redirects.
Basics wrap up
You've got a very basic navigating app, one that can switch between two views when the user clicks a link.
You've learned how to do the following:
- Load the router library.
- Add a nav bar to the shell template with anchor tags,
routerLink
androuterLinkActive
directives. - Add a
router-outlet
to the shell template where views will be displayed. - Configure the router module with
RouterModule.forRoot
. - Set the router to compose HTML5 browser URLs.
- handle invalid routes with a
wildcard
route. - navigate to the default route when the app launches with an empty path.
The rest of the starter app is mundane, with little interest from a router perspective. Here are the details for readers inclined to build the sample through to this milestone.
The starter app's structure looks like this:
router-sample
Here are the files discussed in this milestone.
Comments
Post a Comment