Software systems to innovate and grow

Angular Router, A Complete Guide – Part 2

By Engineering Team December 12, 2016 10 mins read

Recently, we spoke about the basics of Angular router. We covered everything from configuring angular router to adding basic routes to your application. If you’re just getting started with Angular 2 routing, glance over the other article.

We’ve equipped our users with the ability to move around our application. They can see different components based on the route they are on. We can link our users to different parts of our application with RouterLink and highlight those links when the route is active with RouterLinkActive.

We’ve also covered how to add routes in child modules instead of having a massive root.routes.ts file.

But there is so much more in a data-driven application. Many applications move from list view to item view – where a user selects one item from a list and is shown the details of that item in a new route. This means we need to figure out how to send arguments in our routes and read them in the components.

Strap your coding gloves on, because we’ll be covering that and a lot more!

Get the Code

This uses the same code base as the first part of the article. You can clone the code with git clone https://github.com/cartab/angular-router.git.

cd into the directory the project was cloned into and run npm install. You may need to use sudo depending on your permissions and configuration.

You can switch the advanced routing section of the code with git checkout advanced-routing. This branch has all the advanced routing goodness!

Now use ng serve to spin up the application on http://localhost:4200.

Advanced Angular Routing Concepts

How Angular Router Matches Routes

Now let’s step back and look at how Angular Router displays the components based on the routes. For this example let’s have a look at this tree of routes.

Let’s say the user navigates to /university/4/student/3. Angular router will use the array we have provided to look for a match. It starts off with university and matches the university/:id path (based on the prefix matching strategy – discussed below). It will now render the associated component (if any) in the parent <router-outlet>.

Angular router keeps moving forward with the remainder of the URL, i.e. student/3. It will match student/3 and render the associated component in the <router-outlet> present in the UniversityComponent’s template. If no <router-outlet> is present, we’ll see a beautiful error in our console.

If you’re following along with the code, you can see an example of nested routing (when one route’s component is rendered in child component rather than the root component) in the nested-routing branch. Type in git checkout nested-routing into your terminal.

Matching Strategies

Angular router has more than one matching strategy – what the router uses to match the URL the user is on with the paths we provide to the angular router in the configuration.

The matching strategy we saw in the example above is called prefix matching strategy. As the name suggests, a match occurs when the path we specify is a prefix of the remainder of the URL. This strategy is the default one and does not have to be declared explicitly.

Another matching strategy is full. A path that has a full matching strategy can only be matched when the remainder of the URL matches the path completely.

The example above demonstrates how we can use both the routing strategies.

But when would we really need pathMatch: full?

Redirects

In our sample application, the home route corresponds to two paths – '' (the empty path) and 'home' (the home path). Let’s say that we don’t want to show an empty path. We want the user to be on the home path whenever he comes to our page.

We can achieve this with a redirect.

In the code example above, we added another key to our route configuration – redirectTo. This tells angular router to redirect to another URL when the URL matches the path.

And this is exactly why we need full path matching. If we didn’t have that, all the URLs would match the prefix '' (empty path). This means that every request would be redirected to 'home'. Clicking on the work link would redirect us to home first and then to work. We would end up with http://localhost:4200/home/work in our browser’s address bar.

If you’re following along with the code, try changing 'full' to 'prefix' and see what happens when you use the application.

Absolute vs Relative Redirects

If we start our path definition with / we are creating an absolute route. This will replace the existing URL. redirectTo: '/home' would take the user to http://localhost:4200/home regardless of the current URL in the browser.

On the other hand a relative redirect – redirectTo: 'home' would just append home to the existing URL. If the user is on http://localhost:4200/some-route and angular router redirects him based on the relative route to home, the new URL would be http://localhost:4200/some-route/home.

Accessing Angular Router Parameters

Now coming back to the example we gave about moving from a list component to a item component. How can we do that?

We do that by passing parameters in our routes. We can break down the route parameters into two main parts –

1. Declare the parameters in the route

We do this by using the /:param syntax. This can be appended to any route. The most common use for this is when we’d like to display a single item from a list – universities/:universityId/courses/:courseId.

These routes can be constructed using routerLink as well.

  • {{spaceship.name}}

 

The example above shows how angular routerLink directive can be used to add parameters to the routes. The array ['/advanced', 'nasa', spaceship.id] would result in the URL http://localhost:4200/advanced/nasa/:spaceshipId (where spaceshipId would actually be the spaceship id). Note that we used an absolute – `/advanced’ so that clicking on the list multiple times doesn’t give us a really long route that our application can’t handle.

Another way of redirecting the user to a route with an id (or any other data) is using the logic in our component.

You can see that the argument we pass to this.router.navigate has the same syntax as the one we used above with routerLink.

2. Access the parameters in the component

We can access the parameters from the URL using the ActivatedRoute service. It provides an Observable which we can subscribe to.

We subscribe and unsubscribe to the Observable in the ngOnInit and ngOnDestroy methods of the component.

The reason we need to subscribe to the params instead of just using them directly is so that Angular does not have to recreate the component. If we change the route, Angular can update the component directly. This makes our Angular application more efficient, and faster.

To see this in action in our application, see how the relevant spaceship is highlighted when we visit the advanced/nasa/:id route.

Now what if we wanted multiple components to be able to access these parameters from a single route change?

Component-less Routes

We might want some components of our application to share route params. The best way to achieve this is to use componentless routes.

We’ve got two componentless routes in our application. The advanced route has no direct component, but if the user visits that path, he will get to see the WelcomeComponent since that is an empty path that comes under advanced.

The other componentless route is what we use to share resources – nasa/:id. This route has two children with the same path. As strange as that sounds, notice that the second child has another key in its configuration – an outlet. This is what we call an auxiliary route – it maps the component to another router outlet.

Our application can have multiple router outlets. The main router outlet can be without a name, but all others need to have a name attribute. We use this name in angular router configuration to specify which router outlet a component should be rendered in.

You can see that app.component.html has two router outlets –

So if the user navigates to http://localhost:4200/advanced/nasa/:id, the NotificationComponent, as well as the NasaComponent, would be rendered into their respective router outlets. Both those components will have access to the id parameter.

Wildcards

Well, what if the user visits http://localhost:4200/i-just-want-to-go-here?

We don’t have a component that can handle that route!

In such cases, we can use a wildcard – denoted by **. You can see an example of a wildcard in the app.routes.ts file as well as the advanced-life.routes.ts file in the sample application. Here’s another example.

The wildcard comes after all other paths and matches if no other route matches the URL. With this code example in place, if the user went to the route above, he would get to see the 404Component.

Larger Applications and Lazy Loading

If our goal was to build a "hello world" application, we could simply do that with one module and one component. We’d have a fast application and a happy user.

But we are looking to foolproof our application for when it gets larger. When we’ve got 100’s of components and services in our application (and maybe 100’s of routes too).

This is where we can take advantage of the Angular 2’s lazy module loading power. An Angular module (NgModule) is designed so that it can be loaded separately (doesn’t need to be loaded right at the start). This is part of the reason why Angular 2 has significant performance improvements over Angular 1.x.

So how do we take advantage of lazy loading?

Just take a look at the advanced-life module in the sample application that goes along with this article. This module doesn’t load till the user navigates to the any of the routes prefixed with advanced.

This configuration is taken care of in the app.routes.ts file.

We load the advanced module differently. Instead of directly specifying the component(s) that should be loaded when the user navigates to the advanced route, we specify the relative link to the module that can handle the advanced route.

Within the advanced module, we configure the child routes in the advanced-life.routes.ts file.

That’s all we need to add lazy loading to our application.

If we develop our applications in a modular fashion (as we should), lazy loading comes into the picture by default. Writing small components and small modules based on functionality will give you a faster application and more readable code.

Concluding Notes

Angular router gives us some really powerful tools to build fast web applications. Reading about them is only half the battle won. To really understand how these different features of the router come together, play around with the sample code.

Make some mistakes, see some errors, break the application!