Skip to content

Navigation and Routing Basics

Adarsh Kumar Maurya edited this page Dec 10, 2018 · 1 revision

Introduction

A single view does not an application make. Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in these next two chapters, we define routes to navigate between multiple views in our application. Users like to have all of the information they need at their fingertips, so our applications often provide multiple sets of data, in multiple layouts, across multiple views. Routing provides a way for the user to navigate between those many views of the application, whether there are 5, 10 or hundreds. In this chapter, we start with an overview of how routing works in Angular, we examine how to configure routes and tie routes to actions, and we define where to place the routed component's view. Currently, our app component embeds our product list component as a nested component. We instead want to define a set of routes so the user can navigate to the Welcome view, Product List view or Product Detail view. We've already built the product list component, and I've provided the welcome component with the starter files in the GitHub repository for this tutorial. As part of the demos in this chapter, we'll build the shell for the product detail component. When we're finished with this chapter, we'll have a simple application that routes to multiple views.

Generating Code and Handling Undefined

Before we jump into routing, we need more components so we have something to route to. I provided the welcome component as part of the starter files. Let's build the product detail component. The product detail component should ultimately look like this, but we want to focus on routing, not building another component. So let's just create a shell for the product detail component so we can route to it. We could use the Explorer and create new component class and template files manually, or we could use the Angular CLI to automatically create those files. We are going to spend more time on the CLI later in this tutorial, but if you want to try it out now, start by opening a terminal window. I'll use the integrated terminal built in to VS Code. If you don't already have the CLI installed, install it now using npm install -g, for global, @angular/cli. I already have it installed, so won't install it again. Next, we type ng for the Angular CLI, g for generate, c for component, and the name of our component. Since we want to create this component under the products folder, we add the path to the desired component name, products/product-detail, following our naming convention. That's all that is required. By default, however, Angular will create a new folder for this component. For the sample application, we want the contents of our product folder to be flat. We'll use the --flat option to achieve this. We'll learn much more about the Angular CLI later in this tutorial. For now, press Enter, and the Angular CLI creates the component files for us, and it updated our Angular chapter. Yay! We can see here in the Explorer that it created the style sheet and the CSS file, the template in an HTML file, and the class in a TypeScript file. It even created the start of a unit test for us. Let's check out the generated template. Not much here. We'll replace the generated HTML with some placeholder text and use interpolation to bind to a page title. Here, we include the product's name in that title. If we retrieve the product to display using HTTP, which we most likely will, we could have a problem with this code. Any idea what could go wrong here? Recall from the last chapter that HTTP is asynchronous. This means that our page could attempt to display the product's name before the product is retrieved. At that point, the product property is undefined, so we would get a runtime error telling us it can't read the property product name of undefined. How do we ensure this error doesn't happen to us? There are several common ways to prevent undefined property errors. We could use the safe navigation operator defined with a question mark. The safe navigation operator guards against null and undefined values when navigating an object's properties. If the product object is null or undefined, the safe navigation operator simply returns null and does not attempt to access the productName property, hence we don't see an undefined property error. The safe navigation operator is great, but it is not always the best option. For example, it does not work when used with the ngModel two-way binding. It can also be quite tedious when we display many properties, like in our detail template. So there is another option. We can use ngIf and check for the existence of the product object. We add the ngIf to an HTML element that encloses all references to the product object, then we don't need to use the safe navigation operator. Next, let's look at the TypeScript file. The generated component class has all of the basic syntax in place. All it needs is the page title and product properties that we are referencing in the template. Notice that the CLI generated the selector property here. The selector property is only required if the component will be nested within another component. We won't need to nest this component, we'll instead display the component's view as part of the routing, so let's delete the selector. Now let's check out the Angular chapter. Notice that the CLI added the appropriate import and declared our new component. Very nice! Every time we add a component to the application, we need to declare the component in an Angular chapter. We currently have only one Angular chapter, AppModule, so the product detail component and the welcome component must be added to the declarations array for the AppModule. If we use the Angular CLI, as we did in this example, it automatically adds the appropriate declarations. Cool! If we don't use the CLI, like for our welcome component, we need to add the component to the declarations array ourselves. Let's do that now. We add the welcome component to the declarations array, then add the associated import statement. Now that we have the basic components in place, we are ready to add routing to our application.

How Routing Works

An Angular application is a single-page application. That means all of our views are displayed within one page, normally defined in the index.html file. So each of the 5, 10 or hundreds of views take turns appearing on that one page. How do we manage which view to display when? That's the purpose of routing. We configure a route for each component that wants to display its view on the page. As part of our application design, we provide a menu, a toolbar, buttons, images or data links that allow the user to select the view to display. We tie a route to each option or action. When the user selects the option or performs the action, the associated route is activated. Activating a component's route displays that component's view. So for example, the user selects a menu option to display the product list. The product list route is activated, and it displays its view. Hmm. Let's look at that process again with an illustration. Here is the menu that we'll add to our sample application. We tie a route to each menu option using a built-in router directive called routerLink. When the user clicks on the Product List option, for example, the Angular router navigates to the product's route. The browser's location URL changes to match this path segment, and we see /products appear in the address bar. By default, Angular uses HTML5 style URLs, which don't require the hash symbol to indicate local navigation. By using the HTML5 style URLs, you need to configure your web server to perform URL rewriting. How this is done depends on your web server. See the documentation for your web server on how to configure URL rewriting. Angular also supports hash style routing, which does not require URL rewriting. We'll look at how to use hash style routing later in this chapter. When the browser's URL changes, the Angular router looks for a route definition matching the path segment, products in this example. The route definition includes the component to load when this route is activated. In this case, the product list component. The Angular router then loads the component's template. Where does it display this template? Where we specified with the built-in routing directive called router-outlet, and the product list appears. So, that's how routing works. We'll examine these steps in further detail and try them out in demos as we journey through this tutorial chapter.

Configuring Routes

Routing is component-based, so we identify the set of components that we want to provide as routing targets and define a route for each one. Let's see how this is done. An Angular application has one router that is managed by Angular's router service, and we know that before we can use the service we need to register the service provider in an Angular chapter. Similar to the HTTP chapter, Angular provides a RouterModule in the angular/router package that registers the router service provider. To include the features of this external chapter in our application, we need to add it to the imports array of our application's Angular chapter. In addition to registering the service provider, the RouterModule also declares the router directives. In the last clip, we mentioned two router directives, routerLink and router-outlet. By importing the RouterModule, our component templates can use these or any other router directives. RouterModule also exposes the routes we configure. Before we can navigate to a route, we need to ensure that the routes are available to the application. We do this by passing the routes to RouterModule, like this. We call the RouterModule's forRoot method and pass our array of routes to that method. This establishes the routes for the root of our application. If we want to use hash style routes instead of HTML5 style routes, we change this code to set useHash, as shown here. With that, we are ready to configure some routes. The router must be configured with a list of route definitions. Each definition specifies a route object. Each route requires a path. The path property defines the URL path segment for the route. When this route is activated, this URL path segment is appended to the URL of our application. The user can type in or bookmark the resulting URL to return directly to the associated component's view. In most cases, we also specify a component, which is the component associated with the route. It is this component's template that is displayed when the route is activated. These are all examples of route definitions. The first route simply maps the specific URL path segment to a specific component. So this URL displays the template from the ProductListComponent. The :id in the second route represents a route parameter. The Product Detail page displays the detail for one product, so it needs to know which product to display. The product detail component reads the ID from this path segment and displays the defined product. We can define any number of parameters here, separated with slashes. What does this route do? Yep, this URL displays the template from the welcome component. This one defines a default route. The redirect here translates the empty route to the desired default path segment, in this example, the welcome route. A redirect route requires a pathMatch property to tell the router how to match the URL path segment to the path of a route. We only want this default route when the entire client-side portion of the path is empty, so we set the pathMatch to false. The asterisks in the last route denote a wildcard path. The router matches this route if the requested URL doesn't match any prior paths defined in the configuration. This is useful for displaying a 404 Not Found page or redirecting to another route. A few things to note here. There are no leading slashes in our path segments, and the order of the routes in this array matters. The routers uses a first match win strategy when matching the routes. This means that more specific routes should always be before less specific routes, such as the wildcard route. Ready to try this out?

Demo: Configuring Routes

In this demo, we configure the basic routes for our application. We are back in the sample application with the index.html file open. The first step to set up routing is to define a base element in the head tag of the index.html file. Notice that the Angular CLI already did that for us here. This element tells the router how to compose the navigation URLs. Since the app folder is the application route, we'll set the href for the base tag to slash. Now we are ready to configure the route definitions. For that, we go to our Angular chapter, add the appropriate import statement, then add RouterModule to the imports array. This registers the router service provider, declares the router directives, and exposes the configured routes. How does the RouterModule know about our configured routes? We pass them in to the RouterModule by calling the forRoot method. We then configure the routes here by passing them in using an array. Let's start with the product routes. For each route, we specify the path and a reference to the component. The template defined in the specified component will display when the router navigates to this path. Next we add the route to display our welcome page. We'll set the path to welcome and specify the WelcomeComponent. When the application loads, we want to default to the template from the WelcomeComponent, so we'll specify a default route that redirects to our WelcomeComponent, and let's define a wildcard path in case the requested URL doesn't match any prior paths defined in the configuration. This is often used for displaying a 404 Not Found page, but in our simple example, we'll use it to redirect back to the Welcome page. There's a lot of stuff here now in our route application chapter. In a later tutorial chapter, we'll look at how to refactor this chapter into multiple Angular chapters for a separation of concerns.

Tying Routes to Actions

With routing, the user can navigate through the application in several ways. The user can click a menu option, link, image or button that activates or navigates to a route. The user can type the associated URL segment in the address bar after the application URL, or use a bookmark to that URL. Or the user can click the browser's forward or back buttons. The route configuration handles the URLs, so the last techniques will just work. We need to handle the first technique by tying routes to the user actions. We need to decide how we will show the routing options to the user. We could display a navigation pane with links, we can provide a toolbar or images, or we can build a navigation menu, like this one. In a more full-featured application, the menu could have many more options and sub options, but this will do for our purposes. We define that menu as part of this component's template. We then need to tie a route to each menu option. We do that using the routerLink directive. The routerLink is an attribute directive, so we add it to an element such as the anchor tag here, and we enclose it in square brackets. We bind it to a template expression that returns a link parameters array. The first element of this array is the string path of a route. Additional elements can be added to this array to specify optional route parameters. The router uses this array to locate the associated route and build up the appropriate URL based on any provided parameters. When the user selects the option, the associated route is activated. Activating a component route displays that component's view. Now let's add a menu and use the routerLink directive so the user can navigate to the views in our sample application. First, we need to decide how to show the routing options to the user. For our sample application, we'll build a menu. We want to add that menu at the root of our application, so we'll add it to the app component. In the app component template currently, we're nesting the product list component. Now that we are implementing routing, we'll route to the product list component instead. That means that it no longer needs a selector. In the product list component, let's remove that selector. Going back to the app component, we'll replace the nesting with a navigation menu. This menu uses the nav element and the navbar classes from the Twitter Bootstrap styling framework. Let's see how this looks in the browser. Here is our new menu, but clicking on the menu options don't do anything yet and our product list no longer appears. We need to tie routes to these menu options. We'll use the routerLink directive to tie a route to each of these menu options For the Home menu option, we'll add the routerLink to the anchor element, but we could use any clickable element. We want to tie the welcome route to the Home menu option, so we specify welcome here. Pay close attention to this syntax. We assign the routerLink directive to an array defined within quotes. The first element of the array is a string, so it is also enclosed in quotes. We'll use similar syntax to tie the products route to the Product List menu option. Now, we just need to tell Angular where to place our views and display the routed component's template.

Placing the Views

When a route is activated, the associated components view is displayed. But displayed where? How do we specify where we want the routed component to display its view? We use the router-outlet directive. We place that directive in the host component's template. The routed components view then appears in this location. Let's add the router-outlet to our sample application. We are back looking at the app component because it is the host for our router. We add the router-outlet in the template where we want to display the routed components view. We'll put it here. Whenever a route is activated, the associated components view displays here. Let's see how that looks in the browser. Oh my, where did that page come from? That's the welcome page provided in the starter files. When the application launches, the default route is activated and the welcome view displays. If we click on the Product List menu, the routerLink directive activates the product list route and the product list view appears. Sweet! Our application component can now route to multiple views. Notice the URL. The URL segment we defined for the route is displayed here. If we type in something, like welcome, the welcome components view is displayed. Now that we have our routing in place, let's review how these routing features work together. When the user navigates to a feature tied to a route with the routerLink directive, the router link uses the linked parameters array to compose the URL segment. The browser's location URL is changed to the application URL, plus the composed URL segment. The router searches through the list of valid route definitions and picks the first match. The router locates or creates an instance of the component associated with that route. The component's view is injected in the location defined by by the router-outlet directive and the page is displayed. We now have basic routing in our sample application. Yay! As we've seen in this tutorial chapter, routing is rather intricate, requiring code in multiple files, and strings such as parameter names and route paths that must match across those files. So let's finish up this chapter with some checklists that can help ensure all of the bits of routing are in the right places.

Checklists and Summary

To route or to nest, that is the question. When creating components, we need to think about how they will be displayed. For a component's design to be nested within other components, we need to define a selector as part of the component decorator. The selector provides the name of the directive, and then we nest the component within another component using a directive to define where the component template appears. The component does not then need a route. For a component's design to be displayed as a view within our single application page, the component needs no selector, but we do need to configure routes. We then tie those routes to actions; an action activates a route to display the view. If we want to do routing in our application, we need to configure the route definitions, tie routes to actions, and place the view. Let's do checklists for each of these tasks. The first step for doing routing in an application is to configure the routes. Begin by defining the base element in the index.html file. Add RouterModule to an Angular chapter's imports array, then add each route to the array passed to the router chapter's forRoot method, and remember that order matters. The router will pick the first route that matches. Each route definition requires a path, which defines the URL path segment for the route. Be sure the path has no leading slash. Use an empty path for a default route and two asterisks for a wildcard route, which is matched if no prior path matches. Most route definitions also include a component. The component is a reference to the component itself. It is not a string name and is not enclosed in quotes. Once we have the routes configured, we need to tie those routes to actions. First we identify which actions to tie to which routes, then we add the RouterLink directive as an attribute to any clickable element in a component's template. We can use them in menu options, toolbars, buttons, links, images, and so on. Be sure to enclose the router link in square brackets, bind the router link to a linked parameters array. The first element of the linked parameters array is the route's path. All other elements in the array are values for the route parameters. Use the RouterOutlet to identify where to display the routed component's view. This is most often specified in the host component template. When a route is activated, the route component's view is displayed at the location of the router outlet. This chapter was all about navigation and routing. We began with a look at how routing works. We then walked through how to configure routes, tie routes to actions, and define where the routed component's view should appear. Our app component had embedded our product list component as a nested component. In this chapter, we were finally able to remove that nesting. We set up routing so we can now navigate to our welcome and product list components. We've covered the basics of routing in this chapter, but there is so much more. In the next chapter, we'll look at some additional routing techniques and add navigation to the product detail component.