Central to the behavior of single-page applications is the ability to perform navigation without a formal browser page reload. Angular 2 is well-equipped to work around the default browser page reload behavior and allow you to define a routing structure within it, which will make it look and feel like actual page navigation.
The code, links, and a live example of this are available at .
Suppose you have the following function defined globally:
function visit(uri) { // For this recipe, you don't care about the state or title window.history.pushState(null, null, uri); }
The purpose of this is to merely allow you to navigate inside the browser from JavaScript using the HTML5 History API.
In order for Angular to simulate page navigation inside the browser, there are several steps you must take to create a navigable single-page application.
The first step in configuring your application is to specify the base URL. This instructs the browser what network requests performed with a relative URL should begin with.
Anytime the page makes a request using a relative URL, when generating the network request, it will use the current domain and then append the relative URL. For relative URLs, a prepended "/" means it will always use the root directory. If the forward slash is unavailable, the relative path will prepend whatever is specified as the base href. Any URL behavior, including requesting static resources, anchor links, and the history API, will exhibit this behavior.
<base>
is not a part of Angular but rather a default HTML5 element.
Here are some examples of this:
[Example 1] // <base href="/"> // initial page location: foo.com visit('bar'); // new page location: foo.com/bar visit('bar'); // new page location: foo.com/bar // The browser recognizes that this is a relative path // with no prepended / and so it will visit the page at the // same "depth" as before. visit('bar/'); // new page location: foo.com/bar/ // Same as before, but the trailing slash will be important once // you invoke this again. visit('bar/'); // new page location: foo.com/bar/bar/ // The browser recognizes that the URL ends with a /, and so // visiting a relative path is treated as a navigation into a // subpath visit('/qux'); // new page location: foo.com/qux // With a / prepended to the URL, the browser recognizes that it // should navigate from the root domain [Example 2] // <base href="xyz/"> // initial page location: foo.com visit('bar'); // new page location: foo.com/xyz/bar // Base URL is prepended to the relative URL visit('bar'); // new page location: foo.com/xyz/bar // As was the case before, the local path is treated the same // by the browser visit('/qux'); // new page location: foo.com/qux // Note that in this case, you specified a relative path // originating from the root domain, so the base href is ignored
Next, you need to define what your application's routes are. For the purpose of this recipe, it is more important to understand the setup of routing than how to define and navigate between routes. So, for now, you will just define a single catchall route.
As you might suspect, route views in Angular 2 are defined as components. Each route path is represented at the very least by the string that the browser's location will match against and the component that it will map to. This can be done with an object implementing the Routes
interface, which is an array of route definitions.
It makes sense that the route definitions should happen very early in the application initialization, so you'll do it inside the top-level module definition.
First, create your view component that this route will map to:
[app/default.component.ts] import {Component} from '@angular/core'; @Component({ template: 'Default component!' }) export class DefaultComponent {}
Next, wherever your application module is defined, import RouterModule
and the Routes
interface, namely DefaultComponent
, and define a catchall route inside the Routes
array:
[app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {RouterModule, Routes} from '@angular/router'; import {RootComponent} from './root.component'; import {DefaultComponent} from './default.component'; const appRoutes:Routes = [ {path: '**', component: DefaultComponent} ]; @NgModule({ imports: [ BrowserModule ], declarations: [ DefaultComponent, RootComponent ], bootstrap: [ RootComponent ] }) export class AppModule {}
You've defined the routes in an object, but your application still is not aware that they exist. You can do this with the forRoot
method defined in RouterModule
. This function does all the dirty work of installing your routes in the application as well as passing along a number of routing providers for use elsewhere in the application:
[app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {RouterModule, Routes} from '@angular/router'; import {RootComponent} from './root.component'; import {DefaultComponent} from './default.component'; const appRoutes:Routes = [ {path: '**', component: DefaultComponent} ]; @NgModule({ imports: [ BrowserModule, RouterModule.forRoot(appRoutes) ], declarations: [ DefaultComponent, RootComponent ], bootstrap: [ RootComponent ] }) export class AppModule {}
With this, your application is fully configured to understand the route you have defined.
The component needs a place to be rendered, and in Angular 2, this takes the form of a RouterOutlet
tag. This directive will be targeted by the component attached to the active route, and the component will be rendered inside it. To keep things simple, in this recipe, you can use the directive inside the root application component:
[app/app.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'root', template: ` <h1>Root component</h1> <router-outlet></router-outlet> ` }) export class RootComponent {}
That's all! Your application now has a single route defined that will render DefaultComponent
inside RootComponent
.
This recipe doesn't show a very complicated example of routing, since every possible route that you can visit will lead you to the same component.
Nonetheless, it demonstrates several fundamental principles of Angular routing:
RouterModule
. In this example, since there is only one module, you can do this once using forRoot()
. However, keep in mind that you can break your routing structure into pieces and between different NgModules
.<router-outlet>
tag exists. There are many ways in which this can be configured and made more complex, but for the purpose of this simple module, you don't need to worry about these different ways.Angular 2 applications will not raise issues when operating with no form of routing. If your application does not need to understand and manage the page URL, then feel free to totally discard the routing files and modules from your application.
The flow you are hoping your users would go through is as follows:
index.html
.This is the ideal case. Consider a different case:
foo.com/bar
in their URL bar and navigates to it from there directly./bar
and tries to handle the request.Depending on how your server is configured, this might cause problems for you. This is because the last time the user visited foo.com/bar
, no request for that resource reached the server because Angular was only emulating a real navigation event.
This scenario is discussed elsewhere in this chapter, but keep in mind that without a correctly configured server, the user in the second case might see a 404 page error instead of your application.