Telerik blogs
AngularT2 Dark_1200x303

Client-side routing is a key feature in single page applications. Learn how to implement routing in an Angular app with this practical guide.

Angular is a framework for building dynamic client-side applications using HTML, CSS, and JavaScript. It has a nice CLI tool that helps with developer productivity and with generating code that follows the recommended Angular design guide so you can build fast, responsive and modular applications. In this article, you'll learn how to implement routing in an Angular application and how to handle events (e.g button click event).

You should have an understanding of components, directives, modules, data binding, services, and dependency injection in Angular, as those concepts are needed for you to understand what I'm writing about. If you don't know those concepts, you're in good company because I've written about them 😉😃. Here are the links to the articles I've written covering those topics:

  1. A Practical Guide To Angular: Environment and Project Set Up
  2. A Practical Guide To Angular: Components and Modules
  3. A Practical Guide To Angular: Data Binding & Directives
  4. A Practical Guide To Angular: Services & Dependency Injection
  5. A Practical Guide To Angular: Handling HTTP Operations

The application we'll build together while you go through this article builds on the expense tracking application I built from scratch while I wrote the articles listed above. If you have been reading and working along with me over those articles, you should have the complete code. Otherwise, you can download the project on GitHub. When you download it, you should then copy the content from the src-part-5 folder into the src folder if you want to code along while you read.

Adding the History Page

The application only has one page at the moment. That page allows users to view the current month's expenses. We're going to add a new page that will allow users to pick a month and see the expenses for the selected month.

Run the command ng g c expenses/history to generate a new component. Paste the code below in the component's template file.

<div class="row">
  <div class="col-sm-2">
    Select Month:
  </div>
  <div class="col-md-4">
    <input #month (keyup.enter)="getExpense(month.value)" type="month" />
  </div>
</div>
<br />
<et-expense-list [expenses]="expenses" [showButton]="false"></et-expense-list>

This component will display an input element that will be used to select a month and a table that will display the expense for the selected month, rendered through the expense-list component. We use event binding to handle the keyup event for the input element. Event binding is how you listen and subscribe to events in Angular.

The syntax consists of a target event name within parentheses on the left of an equal sign, and a quoted template statement on the right. In the code above it is written as (keyup.enter)="getExpense(month.value)". The keyup event will listen for every keystroke, but we want to only respond when the user presses the enter key. Angular provides the keyup.enter pseudo-event which is raised only when the enter key is pressed.

When the event is raised, it will call the getExpense() function with the value of month.value. The #month declares a variable that references the input element and provides access to the element's data. With it, we can get the value of the element and pass it to the getExpense() function.

Open history.component.ts and update the class with the code below.

import { Component } from "@angular/core";
import { ExpenseService } from "../expense.service";
import IExpense from "../expense";

@Component({
  selector: "et-history",
  templateUrl: "./history.component.html",
  styleUrls: ["./history.component.css"]
})
export class HistoryComponent {
  expenses: IExpense[] = [];
  constructor(private expenseService: ExpenseService) {}

  getExpense(period: string) {
    if (period) {
      this.expenseService.getExpenses(period).subscribe(
        expenses => {
          this.expenses = expenses;
        },
        error => {
          console.log("Error retrieving expenses", error);
        }
      );
    }
  }
}

The implementation for getExpense calls this.expenseService.getExpenses(period) to get an array of IExpense object and then assigns it to the property expenses which is bound to the expense-list component.

Configuring Routes

We have two components that represent two separate pages in the application — the Home component and the History component.

The next thing to do is to enable routing in the application so that users can navigate between pages. To do this, we will first configure the Angular router so that when a user navigates to specific paths, it should render the view of the component that is responsible for that route. The Angular router is managed by the Angular router service, and this service is registered in the RouterModule. The RouterModule also declares some router directives such as the RouterLink and RouterOutlet directives.

The project already has a module called AppRoutingModule, which is where we will put any logic related to routing for the application. This module is declared in app-routing-module.ts and is included in the imports array of the root app module.

@NgModule({
  declarations: [
    AppComponent,
    BriefingCardsComponent,
    ExpenseListComponent,
    HomeComponent,
    HistoryComponent
  ],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent]
})

In order to work with the router service and directives, we need to import the RouterModule. This module is included in the AppRoutingModule’s imports array as you can see in the file

import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";

const routes: Routes = [];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

The routes variable is where you will define the routes for the application and its of type Routes. The Routes type represents an array of Route objects. We will define routes for the application but one thing to notice is the RouterModule.forRoot(routes) call. The forRoot method exposes the routes to the root application.

Update the routes variable with the value below.

const routes: Routes = [
  {
    path: "history",
    component: HistoryComponent
  },
  { path: "", component: HomeComponent },
  { path: "**", redirectTo: "/", pathMatch: "full" }
];

The Route is an object that has a path property which will match the URL path in the browser and a component property which specifies the component that should be rendered when the route matches the specified path. The history path maps to the History component, while the path with an empty string will match the default / route, and is mapped to the Home component.

The ** path indicates a wildcard path, which is what gets called when the requested path in the URL doesn't match any of the defined routes. So if the user visits localhost:4200/dashboard which is not refined, we want to redirect them to the default route /. That is why we specify the redirectTo property, which denotes the path to redirect to when this route definition is matched.

Typically, you’d want to have an error page that non-existing paths gets routed to. The pathMatch property is used to specify the path-matching strategy. By default, the router checks URL elements from the left to see if the URL matches a given path, and stops when there is a match. For example /team/11/user matches team/:id.

Add the following import statement to reference the Home and History components.

import { HistoryComponent } from "./expenses/history/history.component";
import { HomeComponent } from "./home/home.component";

Using the RouterLink and RouterOutlet Directives

Now that we have the routes defined, we want to add links that will enable users to navigate the app. The root App component has a navigation header in the markup. We want the user to browse by clicking any of the two links which should redirect to the actual page. To do this, we're going to use the RouterLink directive. This directive is an attribute directive that we can add to the anchor tag.

Open the app.component.html and update lines 17 to 20 to include the RouterLink directive as an attribute with a value that matches a path.

<a class="nav-item nav-link active" routerLink="/">Home</a>
<a class="nav-item nav-link" routerLink="/history">History</a>

Using that attribute on the anchor tag gives the router control over that element.

We still need one more thing to complete the application. If the route is activated, the router needs to know where to place the view of the component it's supposed to render for that route. This is where you'll use the RouterOutlet directive. It is used as a component directive and indicates the spot in the view where the router should display the component for that route.

With the root App component still open, change line 25 to use the RouterOutlet directive and not the Home component directive.

<router-outlet></router-outlet>

That completes the changes we need to make to enable routing for a SPA Angular application. Test your work by running ng serve -o command to start the Angular application.

angular-routing

Conclusion

Angular can be used to build single-page applications, and client-side routing is a key feature in such applications. In this article, I showed you how to implement routing in an Angular app. You saw how to define routes and use the RouterModule.forRoot() method. You also learned how to use the RouterLink and RouterOutlet directives. There's so much more to routing than what I covered here, and you can read more in the documentation. I covered the fundamentals you need to know to get started building a SPA. If you run into complex requirements, you can check the documentation.

The code for this article can be downloaded from GitHub. It's contained in the src-part-6 folder. If you have any questions, feel free to leave a comment or reach out to me on Twitter.


Peter Mbanugo
About the Author

Peter Mbanugo

Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.

Related Posts

Comments

Comments are disabled in preview mode.