Originally published at techiediaries.com on 06 Jun 2019
In this tutorial part, we’ll learn about the Angular Router v8 by example and will teach you everything you need to start using routing to build Single Page Applications.
Also check out this article for how to add routing to a CRM application.
Note: This tutorial is compatible with both Angular 7 and the new Angular 8 version.
In this tutorial, you'll learn about:
routerLink
,Before diving in practical routing with Angular 8 and Angular Router we’ll be introducing some basic concepts such as client side routing and SPAs. If you are familiar with these concepts, feel free to skip to Angular routing section.
Routing is an important feature for any frontend framework just like in traditional backend frameworks. It allows developers to build modern Single Page Applications (SPAs) that can be loaded once by the browser and provide multiple views that the user can navigate between them using the browser’s address bar and navigation buttons or hyperlinks in the application interface.
Routing is the process of driving the UI of an application using URLs.
Here is the definition of an SPA from Wikipedia:
A single-page application (SPA) is a web application or web site that interacts with the user by dynamically rewriting the current page rather than loading entire new pages from a server.
So in a SPA, only one page is requested from the server. The multiple views that you can transition to are rendered dynamically by the browser not fetched as new pages from the server.
Client side routing is similar in concept to server side routing but uses different mechanisms for implementation. In a traditional web application, you will mostly have multiple pages. Each page maps to a different URL which you can use to access it either directly by entering the URL in the browser’s address bar or via clicking a navigation link or hyperlink in the application UI which has the URL in its href
attribute (e.g <a href="/page1">Page 1</a>
).
In server side routing, when the user navigates to a specific page, the browser sends a request to get the page from the server and display it. In client side routing, the browser has already retrieved all the application in the first place and the logic to show the requested view will be done in the browser with JavaScript.
Here comes the role of the client side (or JavaScript) router.
A client side router is an essential entity in Angular and most frontend frameworks and libraries. It is the code responsible for organizing the application into multiple views (also called screens or states) and navigating between them on user request.
For example, the router will display the home view when the application is first loaded, and then allow the user to navigate to other views like contact or about screens via a navigation menu.
The client side router lets you dynamically display different content depending on the current URL.
Popular client side routers include the Angular Router, React Router and Vue Router.
See this GitHub repository for an example implementation of a JavaScript Router.
URL stands for Uniform Resource Locator and in its simplest definition it’s a web address that references a web page.
Here is the definition of a URL from Wikipedia:
A Uniform Resource Locator (URL), colloquially termed a web address,[1] is a reference to a web resource that specifies its location on a computer network and a mechanism for retrieving it. A URL is a specific type of Uniform Resource Identifier (URI),[2][3] although many people use the two terms interchangeably.[4][a] URLs occur most commonly to reference web pages (http), but are also used for file transfer (ftp), email (mailto), database access (JDBC), and many other applications.
Also watch this video for more information about how URLs work:
Now let’s get started with Angular routing. In this section, we’ll learn the basic concepts behind routing in Angular 8. We’ll introduce the Angular Router and then we’ll proceed to create a simple single page application with Angular 8 that demonstrates the commonly used features of the router.
In more details, you’ll learn about:
HttpClient
,The Angular 8 router is an essential element of the Angular platform. It allows developers to build Single Page Applications with multiple states and views using routes and components and allows client side navigation and routing between the various components. It’s built and maintained by the core team behind Angular development and it’s contained in the @angular/router
package.
You can use the browser's URL to navigate between Angular components in the same way you can use the usual server side navigation.
Angular Router has a plethora of features such as:
prefix
and full
) to tell the Router how to match a specific path to a component,Angular 8 provides a powerful router that allows you to map browser routes to components. So let's see how we can add routing to applications built using Angular 8.
In this section, you’ll learn about various concepts related to Angular routing such as:
routerLink
directive (replaces the href
attribute),Angular applications are built as a hierarchy of components (or a tree of components) that communicate with each other using inputs and outputs. A component controls a part of the screen which is rendered using the component’s template specified as meta information in the @Component
decorator.
A @Component
decorator marks a class as an Angular component and provides configuration metadata that determines how the component should be processed, instantiated, and used at runtime.
In component-based applications such as Angular applications, a screen view is implemented using one or more components.
Routing in Angular is also refereed to as component routing because the Router maps a single or a hierarchy of components to a specific URL.
In Angular, a route is an object (instance of Route) that provides information about which component maps to a specific path. A path is the fragment of a URL that determines where exactly is located the resource (or page) you want to access. You can get the path by taking off the domain name from the URL.
In Angular you can define a route using route configurations or instances of the Route interface.
A collection of routes defines the router configuration which is an instance of Routes.
Each route can have the following properties:
path
is a string that specifies the path of the route.pathMatch
is a string that specifies the matching strategy. It can take prefix
(default) or full
.component
is a component type that specifies the component that should be mapped to the route.redirectTo
is the URL fragment which you will be redirected to if a route is matched.These are the commonly used properties of routes but there are many others. You can find the rest of properties from the official docs.
For example, this is the definition of a route that maps the /my/path/
path to the MyComponent
component:
{ path: 'my/path/', component: MyComponent }
The path can be the empty string which usually refers to the main URL of your application or can be also a wildcard string (**
) which will be matched by the router if the visited URL doesn’t match any paths in the router configuration. This is usually used to display a page doesn’t exist message or redirect the users to an existing path.
The Angular router has a powerful matching algorithm with various built-in and custom matching strategies.
The builtin matching strategies are prefix (the default) and full.
When the matching strategy of a route is prefix, the router will simply check if the start of the browser’s URL is prefixed with the route’s path. If that’s the case, it will render the related component.
This is not always the wanted behavior. In some scenarios, you want the router to match the full path before rendering a component. You can set the full strategy using the pathMatch
property of a route. For example:
{ path: 'products', pathMatch: 'full', component: ProductListComponent}
A full strategy ensures that the path segment of browser’s URL equals exactly the route’s path.
A special case of using the full
property is when you want to match the empty path. Because using the prefix
strategy will match all paths since the empty path prefixes all paths.
For example, we want to redirect the user to the /products
route when they visit our application. This is how our route configuration should look like:
{ path: '', redirectTo: '/products', pathMatch: 'full' }
You can also use custom matcher if the combination of the path property and matching strategy doesn’t help you match your component to a specific URL.
You can provide a custom matcher using the matcher
property of a route definition. For an example, see UrlMatcher.
Dynamic routes are often used in web applications to pass data (parameters) or state to the application or between various components and pages. The Angular router has support for dynamic paths and provides an easy to use API to access route parameters.
You can define a route parameter using the colon syntax followed by the name of the parameter. For example:
{path: 'product/:id' , component: ProductDetailComponent}
In the example, id
is the route parameter. /product/1
, /product/2
, /product/p1
… are examples of URLs that will be matched via this route definition.
The last segment of these URLs are the values of the id
parameter that will be passed to ProductDetailComponent
.
In your matched components, you can access the route parameters using various APIs:
Route guards enables you to allow or disallow access to your specific application routes based on some criteria (for example if the user is logged in or not).
You can also use a guard to prevent users from leaving a component depending on some conditions (for example if a form is not submitted yet and data can be lost).
You can use Angular guards to protect components or complete modules.
To protect a route, you first need to create a guard by sub-classing the CanActivate
interface and overriding the canActivate()
method which needs to return a Boolean value (true
means access is allowed) and add it to route definition via the canActivate
attribute. For example:
{ path: 'product/:id, canActivate:[ExampleGuard], component: ProductDetailComponent}
This is an example implementation of ExampleGuard
:
class MyGuard implements CanActivate { canActivate() { return true; } }
Since the canActivate()
method will always return true
, this guard will always allow access to ProductDetailComponent
.
The Router-Outlet
is a directive exported by RouterModule
and acts as a placeholder that indicates to the router where it needs to insert the matched component(s). The component that has the router outlet is refereed to as the application shell:
<router-outlet></router-outlet>
The Angular router supports more that one outlet in the same application. The main (or top-level) outlet is called the primary outlet. Other outlets are called secondary outlets.
You can specify a target outlet for a route definition using the outlet
attribute.
Angular Router provides two directives for navigation: The routerLink
directive which replaces the href
attribute in the <a>
tags to create links and routerLinkActive
for marking the active link. For example:
<a [routerLink]="'/products'">Products</a>
To show you how to use Angular routing to build a frontend application with multiple screen views, we’ll create an Angular 8 project from scratch using Angular CLI 8.
Note: Please make sure you have Angular CLI 8 installed on your development machine to generate Angular 8 projects.
Angular CLI requires you to have Node 10+ with NPM installed on your machine so without these dependencies you will not be able to install the CLI on your machine. You can easily head to the official website and download the right binaries for your operating system or follow the appropriate documentation for how to install a recent version of Node.js in your system.
As the time of this writing Angular CLI 8.0.1 is installed (npm install -g @angular/cli
)
Open a new terminal on your system, navigate to where you want to create your project and run this command:
$ ng new angular-routing-demo
Before proceeding to generate the project, the CLI will prompt you if:
@angular/router
package in the project and generate a src/app/app-routing.module.ts
file and will also add a <router-outlet>
in the src/app/app.component.html
file which will the shell of our Angular application. In previous versions of Angular CLI, you would need to create this file manually.The CLI will generate the directory structure and the necessary files and will also install the project’s dependencies from npm then gives you control back.
Note: You can also pass a--routing
option to theng new angular-routing-demo
command to tell to add routing in your project without prompting you. This option is also helpful if you are creating apps withng new app
or modules withng new module
and want to automatically setup routing and include a routing module file.
Angular CLI has configured routing in your project and all you have to add is to define route-component mappings after your create your application components but it helps to understand how what steps the CLI has done to setup routing.
If you would like to manually add routing in your application or module, these are the necessary steps you would need to follow:
<base href>
First, you would need to open the src/index.html
file and add a <base>
tag as a child of the <head>
tag **which allows the router to figure out how to compose navigation paths. This is how the index.html
looks like:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Angular Routing Demo</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root></app-root> </body> </html>
The <base href>
tag is not specific to Angular Router. It’s an HTML tag which specifies the base URL for all relative URLs in the page.
Next, you would need to create a routing module inside the main application module and in its own file using a command like this:
$ ng generate module app-routing --module app --flat
The --flat
option tells the CLI to generate a flat file without a subfolder. This way the app-routing.module.ts
file will be created in the src/app
folder along with the app.module.ts
file. This is the content of this module before setting up routing:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; @NgModule({ declarations: [], imports: [ CommonModule ] }) export class AppRoutingModule { }
This is a regular module decorated by the NgModule
decorator and imports CommonModule
.
Note:CommonModule
is a built-in Angular module that exports all the basic Angular directives and pipes, such asNgIf
,NgForOf
,DecimalPipe
, etc.
Next, you would need to open the src/app/app-routing.module.ts
file and update it as follows:
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
We import the Routes
and RouterModule
symbols from the @angular/router
package which is the central repository that holds all the Router APIs.
Next, we declare a routes variable of the Routes
type.
Next, we import RouterModule
via the imports
array of our routing module and we pass in the routes
array to RouterModule
via the forRoot()
method.
And finally, we export RouterModule
from our routing module by adding it to the exports
array.
As you see, the AppRoutingModule
is merely a wrapper around an instance of RouterModule
(returned from the static forRoot()
method) which feeds it the routes
configuration object. It’s now empty but you will add routes to it once you create the components of your application.
Note: RouterModule
is a built-in routing module that exports the Router service and the directives necessary for routing such as RouterLink, RouterLinkActive, RouterLinkWithHref and RouterOutlet.
From the Angular docs:
The forRoot()
static method creates a module that contains all the directives, the given routes, and the router service itself.
In some situations (for submodules and lazy loaded submodules), you would need to use the forChild()
static method instead which creates a module that contains all the directives and the given routes, but does not include the router service.
After setting up the routing module, next you would need to add the router outlet in your main application component. Open the src/app/app.component.html
, this is how it looks like:
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <!-- [...] --> <router-outlet></router-outlet>
The important thing you need to focus on is <router-outlet>
.
The RouterOutlet
is a built-in Angular directive that gets exported from the @angular/router
package, precisely RouterModule
and it’s a placeholder that marks where in the template, the router can render the components matching the current URL and the routes configuration passed to the Router.
Note: The component that contains the router outlet acts like a shell of your application.
Finally, you would need to import AppRoutingModule
in your main application module which resides in the src/app/app.module.ts
file. If you open that file, this is how it looks:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
We import AppRoutingModule
from ./app-routing.module
and add it the imports
array of AppModule
.
That’s it! We’ve seen all the steps that you would need to do by yourself if routing isn’t automatically setup by Angular CLI when you generated your project.
This is not part of how routing works in Angular but for the purpose of our demo application we’ll need to create a service that can be used to get some data to display in our application components. Since we don’t have a backend project which supplies us with data, we can a very useful feature of Angular — the In-Memory Web API available from the angular-in-memory-web-api
package.
This module simulates a backend web application by intercepting the requests from HttpClient
and redirects them to a memory store that you need to create and supply some data in it.
Later when you have a real backend you can simply remove the In-Memory Web API module and all your requests will go to the real backend.
First, let’s start by installing the package from npm using the following command in your terminal:
$ npm install --save angular-in-memory-web-api
Next, let’s create the service that will return the simulated data. In your terminal, run the following command:
ng generate service data
Open the src/app/data.service.ts
file and import InMemoryDbService
from the angular-in-memory-web-api
package:
import { InMemoryDbService } from 'angular-in-memory-web-api';
DataService
must implement InMemoryDbService
and override the createDb()
method:
import { Injectable } from '@angular/core'; import { InMemoryDbService } from 'angular-in-memory-web-api';@Injectable({
providedIn: ‘root’
})
export class DataService implements InMemoryDbService{
constructor() { }
createDb(){let products = [ { id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }, { id: 3, name: 'Product 3' }, { id: 4, name: 'Product 4' }, { id: 5, name: 'Product 5' } ]; return { products };
}
}
The only requirement
is that each object in the data array should have a unique id
Next, you need to import InMemoryWebApiModule
and DataService
in the src/app/app.module.ts
file and add them in the imports
array:
import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule } from ‘@angular/core’;
import { AppRoutingModule } from ‘./app-routing.module’;
import { AppComponent } from ‘./app.component’;
import { HttpClientModule } from “@angular/common/http”;
import { InMemoryWebApiModule } from “angular-in-memory-web-api”;
import { DataService } from “./data.service”;@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule,
InMemoryWebApiModule.forRoot(DataService),
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
We also imported HttpClientModule
because we’ll need to HttpClient
to send API requests.
Next, create another service for working with products. In your terminal, run:
$ ng generate service product
Next, open the src/app/product.service.ts
file and update accordingly:
import { Injectable } from ‘@angular/core’;
import { HttpClient } from “@angular/common/http”;@Injectable({
providedIn: ‘root’
})
export class ProductService {
API_URL: string = “api/”;constructor(private httpClient: HttpClient) { }
getProducts() {
return this.httpClient.get(this.API_URL + ‘products’)
}getProduct(productId) {
return this.httpClient.get(${this.API_URL + 'products'}/${productId}
)
}
}
We added the API_URL
string that holds the address of the API server.
Next, we imported and injected HttpClient
and finally we defined the two getProducts()
and getProduct(productId)
methods.
Next, let’s create a P
roduct class that will act as a data model for the product type. In your terminal, run:
$ ng generate class product
Open the src/app/product.ts
file and add the following code:
export class Product {
id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
Now that you have a project with routing setup and data services created, you need to create the components of your application. You can easily generate components using the Angular CLI.
Head back to your terminal and run the following commands:
$ ng generate component product-list
$ ng generate component product-detail
We create two components. The product list component which displays a list of products. When you click on a specific product you’ll be taken to the product detail component which displays that single product.
Now let’s add an implementation for ProductListComponent
.
Open the src/app/product-list/product-list.component.ts
file update it accordingly
import { Component, OnInit } from ‘@angular/core’;
import { ProductService } from “…/product.service”;
import { Product } from “…/product”;@Component({
selector: ‘app-product-list’,
templateUrl: ‘./product-list.component.html’,
styleUrls: [‘./product-list.component.css’]
})
export class ProductListComponent implements OnInit {
products: Product[] = [];constructor(private productService: ProductService) { }
ngOnInit() {
this.productService.getProducts().subscribe((products: Product[])=>{
this.products = products;
console.log(products);
})
}
}
We import ProductService
and Product
from their respective paths.
Next we define a products
variable of type Product[]
and we initialize it with an empty array.
Next, we inject ProductService
as productService
via the component constructor.
Finally on the ngOnInit()
life-cycle event of the component we subscribe to the Observable returned from calling the getProducts()
method and we assign the fetched products to the products
array.
Now let’s display the list of products in the src/app/product-list/product-list.component.html
file using the following code:
<div>
<h1>
Products
</h1>
<ul>
<li *ngFor=“let product of products”>
{{ product.name }}
</li>
</ul>
</div>
We simply use an Angular ngFor
directive to iterate over the products
array and display each product’s name.
Let’s also implement the product detail component.
Open the src/app/product-detail/product-detail.component.ts
file and add the following code:
import { Component, OnInit } from ‘@angular/core’;
import { ProductService } from “…/product.service”;
import { Product } from “…/product”;@Component({
selector: ‘app-product-detail’,
templateUrl: ‘./product-detail.component.html’,
styleUrls: [‘./product-detail.component.css’]
})
export class ProductDetailComponent implements OnInit {
product: Product = new Product(-1,‘No Product’);
constructor(private productService: ProductService) { }
ngOnInit() {
this.productService.getProduct(1).subscribe((product: Product) =>{
this.product = product;
})
}
}
We import ProductService
and Product
from their paths.
Next, we define a product variable of type Product
that will hold a product and we initialize it with a non existent product.
Next, we inject ProductService
as productService
.
Finally, on the ngOnInit()
event, we call the getProduct()
method of ProductService
to retrieve a product with id 1 and assign it to the product
variable.
We are creating a product variable which will hold the product to display in the template. For now, it holds the product with the hard-coded id 1 but later we’ll see how we can use the route parameter as a source to get the appropriate product by id to store in this variable.
Now open the src/app/product-detail/product-detail.component.html
and add the following code:
<div>
<h1>
Product #{{product.id}}
</h1>
<p>{{ product.name }}</p>
</div>
After creating and implementing the components of our application, now you need to add them to the router.
Open the src/app/app-routing.module.ts
file and start by importing your components:
import { ProductListComponent } from “./product-list/product-list.component”;
import { ProductDetailComponent } from “./product-detail/product-detail.component”;
Next, add the following object to the routes
array:
{path: ‘products’ , component: ProductListComponent},
This first route links the /products
path to ProductListComponent
so when the /products
URL is visited, the router will render the product list component.
Next, add the second route:
{path: ‘product/:id’ , component: ProductDetailComponent}
This will link routes with dynamic IDs like /product/1
or /product/9
etc. to ProductDetailComponent
.
Note: Please note that at this point ****ProductDetailComponent
is linked to the dynamicproduct/:id
path but doesn’t have the logic to get the value of the id from the route. This will be handled in the next tutorial.
We can also add this route which will redirect the empty route to /products
so whenever the user visits the empty path they will be redirected to the products component:
{ path: ‘’, redirectTo: ‘/products’, pathMatch: ‘full’ },
pathMatch is used to specify the matching strategy full or prefix. full means that the whole URL’s path needs to match by the matching algorithm. prefix means the first route where path matches the start of the URL will be chosen. In the case of empty paths if we don’t set the full matching strategy then we won’t get the desired behavior as any path starts with an empty string.
The last thing you need to do is to add the navigation links that take you from one component to another.
Angular provides the routerLink
and routerLinkActive
directives that need to be added to the <a>
anchors.
routerLink
directive needs to used instead of the href
attribute.routerLinkActive
directive is used to add a CSS class to an element when the link’s route becomes active.Open the src/app/product-detail/product-detail.component.html
file and add a link to navigate to the list of products:
<a [routerLink] = “‘/products’”>
Go to Products List
</a>
Next, open the src/app/product-list/product-list.component.html
file and add a link to each product to take you to the product details component:
<div>
<h1>
Products
</h1>
<ul>
<li *ngFor=“let product of products”>
<a routerLink= “/product/{{product.id}}”>
{{ product.name }}
</a>
</li>
</ul>
</div>
This is the end of this first tutorial of our series to learn Angular routing with Angular 8 (most of it is also valid for v7 or previous versions).
We have learned about the basic concepts of client side routing, next we’ve seen the basic concepts of the router and finally created a Single Page Application with basic building such as components, services and modules.
We have seen how you can automatically setup routing in your Angular 8 application using the CLI v8 and how you can simulate a backend server using the In-memory Web API without actually having a backend server.
In the next tutorial we’ll see how we can access route parameters in our components.
You can find the source code of this application demo from this GitHub repository.
Originally published at techiediaries.com on 06 Jun 2019
==========================================
Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter
☞ Angular 8 (formerly Angular 2) - The Complete Guide
☞ Complete Angular 8 from Zero to Hero | Get Hired
☞ Learn and Understand AngularJS
☞ The Complete Angular Course: Beginner to Advanced
☞ Angular Crash Course for Busy Developers
☞ Angular Essentials (Angular 2+ with TypeScript)
☞ Angular (Full App) with Angular Material, Angularfire & NgRx
☞ Angular & NodeJS - The MEAN Stack Guide
#angular #web-development