Angular Router Jumpstart Book
Angular Router Jumpstart Book
Note: see details at the end of this book for getting the Free Angular For
Beginners Course, plus a bonus content Typescript Video List.
Auxiliary Routes: what are they and when are they useful?
What does the URL look like for accessing an auxiliary route?
Auxiliary Routes
Introduction
Welcome to the Angular Router Jumpstart Book, thank you for joining!
Like the title says, the goal of the book is to get you proficient quickly in
the Angular Router.
Then we will take that knowledge and we will apply it to a concrete use
case: we will build the navigation system of a small application.
I hope you will enjoy this book, please send me your feedback at
[email protected].
What is a SPA?
Let’s start with the first question: Why Single Page Applications (SPAs)?
This type of applications have been around for years, but they have not
yet become widespread in the public Internet.
There is both a good reason for that and an even better reason on why
that could change in the near future.
SPAs did get adopted a lot over the last few years for building the private
dashboard part of SaaS (Software as a Service) platforms or Internet
services in general, as well as for building enterprise data-driven and
form-intensive applications.
The answer is no, it does not, but that is an interesting possibility that it
brings, given the many benefits of building an application that way.
Which brings us to a key question:
Let’s start with a feature that does not get mentioned very often:
Production Deployment.
If you start navigating around, you will see that the page does not fully
reload – only new data gets sent over the wire as the user navigates
through the application – that is an example of a single page
application.
Using the Chrome Dev Tools, let’s inspect the index.html of the
AngularUniv website that gets downloaded on the home page (or any
other page, if you navigate and hit refresh).
1 <meta charset="UTF-8">
2 <head>
5 rel="stylesheet" type="text/css"/>
7 <link rel="stylesheet"
8 href="https://angular-university.s3.amazonaws.com
9 /bundles/bundle.20170418144151.min.css">
10 </head>
11
12 <body class="font-smoothing">
13
14 <app>... </app>
15
16 <script
17 src="https://angular-university.s3.amazonaws.com
18 /bundles/bundle.20170418144151.min.js"></script>
19
20 </body>
21
The page that gets downloaded is the very first request that you will see
in the Chrome Network tab panel – that is the literally the Single Page of
our Application.
Let’s notice one thing – this page is almost empty! Except for the tag,
there is not much going on here.
Note: on the actual website the page downloaded also has HTML that was
pre-rendered on the server using Angular Universal
Notice the names of the CSS and Javascript bundles: All the CSS and
even all the Javascript of the application (which includes Angular) is not
even coming from the same server as the index.html – this is coming
from a completely different static server:
In this case, the two bundles are actually coming from an Amazon S3
bucket!
Also, notice that the name of the bundles is versioned: it contains the
timestamp at which the deployment build was executed that deployed
this particular version of the application.
Single Page Applications are super easy to deploy in Production, and even to
version over time!
These 3 static files can be uploaded to any static content server like
Apache, Nginx, Amazon S3 or Firebase Hosting.
Of course the application will need to make calls to the backend to get
its data, but that is a separate server that can be built if needed with a
completely different technology: like Node, Java or PHP.
We can configure the server that is serving our SPA with a parameter
that specifies which version of the frontend application to build: it's as
simple as that!
This type of deployment scales very well: static content servers like
Nginx can scale for a very large number of users.
Of course, this type of deployment and versioning is only valid for the
frontend part of our application, this does not apply to the backend
server. But still, it's an important advantage to have.
On a SPA, after the initial page load, no more HTML gets sent over the
network. Instead, only data gets requested from the server (or sent to
the server).
So while a SPA is running, only data gets sent over the wire, which takes
a lot less time and bandwidth than constantly sending HTML. Let’s have
a look at the type of payload that goes over the wire typically in a SPA.
1 {
2 "summary": {
3 "id": 9,
4 "url": "angular2-http",
5 "description": "Angular RxJs Jumpstart",
6 "longDescription": "long description goes here",
7 "totalLessons": 15,
8 "comingSoon": false,
9 "isNew": false,
10 "isOngoing": false,
11 "visibleFrom": "1970-01-31T23:00:00.000Z",
12 "iconUrl": "https://angular-university.s3.amazonaws.comangular-http-v2.png
13 "courseListIcon": "https://angular-academy.s3.amazonaws.com/observables_rxjs.png
14 },
15 "lessons": [...]
16 }
17
18
For example, things like the headers and the footers of a page are
constantly being sent to the browser but they have not really changed.
Until relatively recently, search engines like Google had a hard time
indexing correctly a single page application. But what about today?
and have Angular bootstrap on the client side and take over
the page a SPA
But this leaves the last question of this section still unanswered. You
must be thinking at this stage after reading the last few sections:
If only minimal HTML is loaded from the server at startup time, then how
does the page keep changing over time?
Which leads us to the last question of this section, and maybe the most
important:
The answer is simple, and it has to do with the way that single page
applications actually work when compared to traditional server-based
applications.
We can also split the bundles into multiple parts if needed using
code splitting.
Its today simpler than ever to make SEO-friendly SPAs, so they will likely
have increased adoption in the future, in scenarios where SPAs have not
been frequently used.
After the startup, only data gets sent over the wire as a JSON
payload or some other format. But no HTML or CSS gets sent
anymore over the wire after the application is running.
The key point to understand how single page applications work is the
following:
instead of converting data to HTML on the server and then send it over the
wire, in a SPA we have now moved that conversion process from the server
to the client.
The conversion happens last second on the client side, which allows us
to give a much-improved user experience to the user.
I hope that this convinces you of the advantages of SPAs. If not please
let me know and why that is not the case so I can better develop some
of the points.
Also, at this stage, I want to show you some code straight away. Let’s
build a small Hello World SPA in Angular, and take it from there. We are
going to see how the concepts that we have introduced map to the
small application that we are about to build.
Auxiliary Routes: what are they and when are they useful?
3 path: 'home',
4 component: Home
5 },
6 {
7 path: 'lessons',
8 component: AllLessons
9 }
10 ];
11
1 @Component({
2 selector:'app',
3 template: `
4
5 <main>
6 <router-outlet></router-outlet>
7 </main>
8 `
9 })
12 }
13
4
5 @NgModule({
6 declarations: [App],
8 bootstrap: [App]
9 })
11 }
12
13 platformBrowserDynamic().bootstrapModule(AppModule);
14
This is something that it's better to solve from the very beginning,
otherwise we will not have a good router development experience, and
the application won't be usable in production. Let's go over the problem.
This means that when the router navigates to /lessons , that URL is
really shown in the browser address bar. This is opposed to the ancient
routing were the browser address bar would display /#/lessons
instead.
This is because the # part of the URL was ignored by the server.
But now in the new strategy, this will cause an attempt to load a file
called lessons , which does not exist, and so we get 404 Not found.
1 function redirectRouterLessonUnmatched(req,res) {
2 res.sendFile("index.html", { root: './index.html' });
3 }
4
5 app.use(redirectRouterLessonUnmatched);
6
7
This would give us a good start in using the new HTML5 mode of the
router, without this the router usability is compromised. Another
common source of issues is, how to configure a default or a fallback
route, this is probably something that all applications need.
3 {
4 path: "",
5 component: Home
6 },
7 {
8 path: "**",
9 component: PageNotFoundComponent
10 }
11 ];
12
We can see that the empty path configuration would map the URL / to
the component Home , and all other paths to PageNotFoundComponent .
But there is a catch in this configuration as well.
That's why we should put the fallback route configuration as the last
entry in the array. With this baseline configuration, let's now set up some
router navigation. There are two ways of doing this:
1 <ul class="top-menu">
2 <li>
3 <a routerLink="">Home</a>
4 </li>
5 <li>
6 <a routerLink="courses">Courses</a>
7 </li>
8 <li>
9 <a [routerLink]="['lessons']">Lessons</a>
10 </li>
11 </ul>
12
We can either hardcode a string directly in the template, like its the case
of the home route or the courses route. But we can also pass it an
expression. If so we need to pass it an array containing the multiple URL
path parts that we want to navigate to: in this case we want to navigate
to the /lessons path.
1
2 constructor(private router:Router) {
3
4 }
5
6 openCourse(course) {
7 this.router.navigateByUrl(`/courses/${course.id}`);
8 }
9
10 showCoursePlayList(course) {
11 this.router.navigate(['/courses',course.id]);
12 }
13
the URL:
1 constructor(router: Router) {
2
3 route.params.subscribe(
4 params =>{
5 this.courseId = parseInt(params['id']);
6 }
7 );
8 }
For that, the reactive router introduces also the notion of snapshot. A
snapshot can be injected in the constructor of the routed component:
1
2 constructor(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
3
4 const parentRouteId = state.parent(route).params['id'];
5
6 ...
7 }
And this is just one of the many new features of the new router. The new
router also implements the usual features of routing, such as Child
routes which allow us to implement common UI patterns like Master
Detail.
This sounds a lot like what was happening with our top-level routing
configuration. Only /home or /lessons can be active at any given time.
In fact, using the empty path routing feature and making the top-level
route a componentless route, we can rewrite the configuration using
child routes:
2 {
3 path: '',
4 children: [
5 {
6 path: 'home',
7 component: Home
8 },
9 {
10 path: 'lessons',
11 component: AllLessons
12 }
13 ]
14 }
15
16 ];
17
This gives the exact same result as before: only /home or /lessons can
be active at one given time. Remember all this route config only has one
router-outlet , so the end result of the matching must be a single
Imagine a course with a list of lessons. You click on a lesson in the list
in order to display it. But there is a catch, there are multiple types of
lessons: video lessons, text lectures, quizzes or interactive exercises.
2 {
3 path: 'courses',
4 children: [
5 {
6 path: ':id',
7 children: [
8 {
9 path: '',
10 component: CourseLessons,
11 },
12 {
13 path: 'videos/:id',
14 component: VideoLesson
15 },
16 {
17 path: 'textlectures/:id',
18 component: TextLesson
19 },
20 {
21 path: 'quizzes/:id',
22 component: QuizLesson
23 },
24 {
25 path: 'interactivelessons/:id',
26 component: InteractiveLesson
27 }
28 ]
29 }
30 ]
31 }
32 ];
33
This is one way to do it, we have gone here into several levels of nesting
to show that it's possible.
The way this works is that when the user clicks in one of the lessons,
depending on the link clicked a new detail screen will show replacing the
master CourseLessons component.
2 <router-outlet></router-outlet>
3 <router-outlet name="section1"></router-outlet>
4 <router-outlet name="section2"></router-outlet>
5 <router-outlet name="section3"></router-outlet>
6 </div>
But how can this work, because all matching is done using the URL, and
there is only one URL. Right ?
Imagine that you divide your browser window into multiple mini-browser
windows, each with its own separate URL. Then you provide separate
routing configuration for each of those windows because you would
want those windows to be navigated separately. Here are some
examples.
It's very common for applications to divide the page into multiple
regions:
popup dialogs for editing a list detail, that you want to keep
upon during navigation
2 {
3 path: 'courses',
4 ....
5 },
6 {
7 path: 'playlist',
8 outlet: 'aside',
9 component: Playlist
10 }
11 ];
12
What we have configured here is that when the path of the aside outlet
is set to playlist , we are going to display the component Playlist .
This routing can be defined independently of the primary route.
Let's see how does this work, how can the URL be used to display two
URLs instead of one?
/lessons(aside:playlist)
We can see that /lessons would still route the primary route to the
AllLessons component. But inside parentheses, we have an auxiliary
route. First, we have the name of the outlet to which it refers to: aside .
Then we have a colon separator and then we have the url that we want
to apply to that outlet, in this case /playlist . This would cause the
Playlist component to show in place of the aside outlet.
Note that you could have multiple auxiliary routes inside parenthesis,
separated by // . for example this would define the url for a left-menu
outlet:
`/lessons(aside:playlist//leftmenu:/some/path)`
And with this in place, we have at this point covered many scenarios
that are commonly used while building the navigation system of an
application.
Let's now put all of this in practice in the next section, as well as
introduce a couple of extra scenarios.
We will do this step by step, the goal here is to learn how to configure
the Angular Router by example and learn how to implement some of the
very common routing scenarios that you will likely find during your
everyday development.
Child Routes
Auxiliary Routes
a top menu
A navigation breadcrumb
9 </div>
10 </nav>
11
12 <router-outlet></router-outlet>
13
Also notice the router-outlet tag: this means the main content of the
page below the top menu will be placed there. Also notice that there is
no side navigation bar at this stage. The side navigation should only be
visible if we click on Courses . Let's now write the router configuration
for the top menu.
2 {
3 path: 'home',
4 component: HomeComponent
5 },
6 {
7 path: 'about',
8 component: AboutComponent
9 },
10 {
11 path: 'courses',
12 component: CoursesComponent
13 },
14 {
15 path: '',
16 redirectTo: '/home',
17 pathMatch: 'full'
18 },
19 {
20 path: '**',
21 redirectTo: '/home',
22 pathMatch: 'full'
23 }
24 ];
As we can see, the home , about and courses Url paths currently map
to only one component. Notice that we also configured a couple of
redirects for the empty path case and a fallback route using the **
wildcard. This is a good start, we have defined the home page, handled
invalid URLs and added a couple of common navigation items.
After setting up the top menu, we will now implement the following:
As we can see, the main content of the page (everything below the top
menu) was applied in place of the router outlet. But this Courses page
will also have other internal routing scenarios as we will see further.
1 <main>
2 <div class="container">
5 <ol class="breadcrumb">
9 <div class="jumbotron">
10 <h1>Course Categories!</h1>
12 </div>
13 <router-outlet></router-outlet>
14 </div>
15 <router-outlet name="sidemenu"></router-outlet>
16 </div>
17 </div>
18 </main>
We will go over the side menu outlet in a moment, but right now we want
to configure the router to include the course category cards component
in place of the unnamed router outlet.
1 {
2 path: 'courses',
3 component: CoursesComponent,
4 children: [
5 {
6 path: '',
7 component: CourseCardsComponent
8 }
9 ]
10 }
This configuration means that if we hit the /courses URL, the following
occurs:
But how do we display the side menu? For that, we are going to need
what is sometimes referred to an auxiliary route.
Auxiliary Routes
Auxiliary route is a common term to describe this scenario where we
have multiple portions of the screen whose content is dependent upon
the value of the url. In terms of router configuration, this corresponds to
a route with a non-default outlet name.
1 @Component({
2 selector: 'app-categories-menu',
3 templateUrl: './categories-menu.component.html',
4 styleUrls: ['./categories-menu.component.css']
5 })
7
8 constructor(route: ActivatedRoute) {
9
10 route.params.subscribe(params => console.log("side menu id parameter"
11
12 }
13 }
We can see here that the side menu component gets notified whenever
a change in the URL occurs, which is the essential part of making the
side menu adaptable to the URL content.
3 component: CoursesComponent,
4 children: [
5 {
6 path: '',
7 component: CourseCardsComponent
8 },
9 {
10 path: '',
11 outlet: 'sidemenu',
12 component: SideMenuComponent
13 }
14 ]
15 }
What this will do is, whenever we navigate to /courses we will now get
the following (referring back to the file courses.component.html above):
We can start seeing here a pattern: a large variety of many of the routing
scenarios that we typically want to implement can be configured by
using only a few configuration notions: routes, outlets, child routes.
Let's see a further example of this, where we will go one navigation level
deeper in the application routing!
we would like at the same time that we click on Development for the
side menu to change its content as well, by displaying a list of links
specific to the Development course category.
So in this scenario, we will have two different sections of the page that
will react separately to the URL change: the side menu and the main
body of the Courses page.
1 {
2 path: 'courses',
3 component: CoursesComponent,
4 children: [
5 {
6 path: '',
7 component: CourseCardsComponent
8 },
9 {
10 path: ':id',
11 component: CoursesCategoryComponent
12 },
13 {
14 path: '',
15 outlet: 'sidemenu',
16 component: SideMenuComponent
17 }
18 ]
19 }
Notice that we are using a :id path variable, to capture the URL part of
the courses section. The content of this variable will be for example
development or it-software .
2 <h2>Development</h2>
3 <p>... </p>
5 </div>
8 <p>... </p>
9 <p><a class="btn btn-secondary" routerLink="it-software" role="button
10 </div>
Note that this won't work in the sense that the side menu will not be
notified of this navigation, more on this later.
This would display the CoursesCategoryComponent in the main body of
the Courses page as expected. But what if we wanted the side menu to
adapt its contents according to the navigation as well?
With the current configuration this would not happen, the side menu
would still display the same content.
1 {
2 path: 'courses',
3 component: CoursesComponent,
4 children: [
5 {
6 path: '',
7 component: CourseCardsComponent
8 },
10 {
11 path: ':id',
12 component: CoursesCategoryComponent
13 },
15 {
16 path: '',
17 outlet: 'sidemenu',
18 component: SideMenuComponent
19 },
20 {
21 path: ':id',
22 outlet: 'sidemenu',
23 component: SideMenuComponent
24 }
25 ]
26 }
content of the router outlet named sidemenu was kept the same.
the page has multiple browser windows each with its own URL, one for
the main content and the other for the side menu.
You could also do this via the template, but it's great to be able to
leverage Typescript auto-completion to fill in the needed navigation
parameters. In these more advanced navigation scenarios auto-
completion really helps as it acts like a form of documentation that is
always up-to-date.
To do the navigation let's first change the HTML template of the course
section card:
3 <p>...</p>
5 </div>
1 @Component({
2 selector: 'app-course-cards',
3 templateUrl: './course-cards.component.html',
4 styleUrls: ['./course-cards.component.css']
5 })
7
8 constructor(private router: Router, private route: ActivatedRoute) {
9
10 }
11
12 navigate(path) {
13 this.router.navigate([{outlets: {primary: path, sidemenu:path}}],
14 {relativeTo: this.route});
15 }
16
17 }
18
We can see that we are using the router navigation API to navigate in
two outlets at the same time: the primary outlet and the sidemenu
outlet.
With this latest navigation logic, the side menu is now notified of the
URL change. This means that the side menu could now load a different
set of URLs to be displayed if needed.
/courses/(development//sidemenu:development)
Notice that many of the terms that we usually use to describe routing
scenarios like nested routes don't actually have a direct correspondence
in the routing configuration terminology.
This is is because we can obtain those scenarios by combining a couple
of more generic concepts: a nested route can be obtained via a child
route with a different component than the parent route, an auxiliary
route is just a normal route with a non-default outlet name, other than
that it has the properties of a primary route, etc.
As you could see, the Router is very powerful. With only a few concepts
we can cover a vast variety of configuration scenarios.
But because the router concepts are very generic, so it's not always
obvious how to map them to certain use cases, so I hope this book
helped with that.
If you have any questions about the book, any issues or comments I
would love to hear from you at [email protected]
I invite you to have a look at the bonus material below, I hope that you
enjoyed this book and I will talk to you soon.
Kind Regards,
Vasco
Angular University
Typescript - A Video List
In this section, we are going to present a series of videos that cover
some very commonly used Typescript features.