Building Single Page Applications (SPA) with Angular Router

Posted by: V Keerti Kotaru , on 12/1/2018, in Category AngularJS
Views: 43360
Abstract: Routing is an important aspect of building a SPA (Single Page Application). In this two-part Basic to Advanced tutorial on Angular Routing, we will discuss all the important components required to implement routing in your applications.

Routing in Angular enables navigation from one page to another while the user performs application tasks. Routing also allows you to define the modular structure of your application. However, there is much more to routing than just moving the user between multiple views of an application.

In this Angular Routing - Basic to Advanced tutorial, we will learn:

Part 1

  • basics of routing with Angular
  • the need for routing
  • getting started instructions to add Routing to an Angular project
  • configurations for routes
  • creating links that perform view transitions

Part 2

  • asynchronously resolving a route after obtaining data
  • conditionally redirecting to an alternate route
  • authorization checks on a route etc.
  • and more.

This tutorial is from the DotNetCurry(DNC) Magazine with in-depth tutorials and best practices in JavaScript and .NET. This magazine is aimed at Developers, Architects and Technical Managers and covers Angular, React, Vue.js, C#, Design Patterns, .NET Core, MVC, Azure, DevOps, ALM, and more. Subscribe to this magazine for FREE and receive all previous, current and upcoming editions, right in your Inbox. No Spam Policy.

Angular Routing – Basic to Advanced

Off late Single Page Applications (SPAs) have become a norm with rich web UI applications being built as SPAs.

SPAs have a unique advantage that the user doesn’t lose context between page transitions. Yester-year applications reload the entire page as the user tries to navigate between views. The flicker not only affects user experience, it is also inefficient. An update to a section of the page needs the whole page to reload.

SPAs address this problem.

Imagine a travel website. One of the screens show a list of destinations. The User clicks on a link in the list and the whole page loads to show the details, although the Page header, footer, navigation controls (like breadcrumbs, left nav) etc. haven’t reloaded at all. They might have been updated to show the latest content, but they haven’t reloaded the whole view. This leads to a better and rich user experience.

Editorial note: A way to circumvent the entire page reload problem is to use AJAX which would allow the client to communicate with the server, without reloading the entire page. However, a key difference between SPAs and AJAX is that SPA is a ‘paradigm’ for how to develop web apps and it may or may not use AJAX to achieve its goal. AJAX on the other hand is a ‘technique’ about communicating with the network and server in the background.

Angular provides router API to manage a SPA. The Angular Router takes care of the following.

- Allows a URL to represent a view in the application. For example, my-travel-website.com/destinations represents a list screen. And my-travel-website.com/destination/hyderabad represents a view that shows detailed information about the travel destination Hyderabad.

- Create links that can preform view transitions. In other words, navigate from one view to the other.

- Allow creating deep-links to specific pages in the application. Without routing and URL tied to a view, the user doesn’t have a way to deep-link to a screen and would need to go through the screens before it, every single time. Users can’t bookmark or save a link on the page. Routing solves this problem.

For example, if a user wants to get to the details screen for London, he/she may navigate directly to my-travel-website.com/destination/london. The User doesn’t need to navigate to the list screen and subsequently click on London.

- It is convenient for users to go back and forward using browser buttons. Routing  doesn’t take away the ease of using browser back and forward buttons or actions.

Part 1 - Getting Started with Angular Router

If you are using Angular CLI for getting started with a new Angular project, use the following command to include routing.

ng new my-travel-app –-routing

The --routing flag indicates routing to be added to the solution. It does the following to add routing features.

1. Install Angular router from the MonoRepo. To install it manually use the following command

npm install -S @angular/router

2. It’s preferred to separate routing logic to an Angular module of its own. Hence, a module named AppRoutingModule is created in a file app-routing.module.ts

3. Import Routes and RouterModule from @angular/router

4. Configure routes. Next section explains route configuration in details.

5. Provide route configuration as a parameter to forRoot function on the RouterModule. Import the returned module.

@NgModule({        // routes has the route configuration. 
// It is a variable of type Routes
  imports: [RouterModule.forRoot(routes)], 
  exports: [RouterModule]
})
export class AppRoutingModule { }

Also note that the RouterModule is exported. It allows the routing to be consumed in the root module where the routing is used.

Notice the code snippet in app.module.ts. It is the root module in the sample application.

import { AppRoutingModule } from './app-routing.module';


@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Route configuration

As described earlier, SPAs use routing to tie a view to a URL (which is nothing but a route). Route configuration defines route pattern (or the URL pattern). It ties a component for a given route pattern. A Component renders the view.

A route configuration object starts with two basic fields:

url – route pattern

component – the component to render when a route pattern matches.

Any application will have more than one routes to configure. Hence route configuration is an array of objects. Consider the following routes.

const routes: Routes = [
  {
    path: "destinations",
    component: DestinationListComponent
  },
  {
    path: "destination/:city",
    component: DestinationDetailsComponent
  }
];

In the sample application detailed in this article, we have two views

1. List screen, showing list of destinations to travel. When the URL matches destinations (see Figure 1), DestinationListComponent is rendered. It shows a view for list of cities.

2. Details screen, showing a detailed view of the selected city.

list-screen

Figure 1: List screen

Notice that city, is a route parameter. Hence, it’s qualified with a colon. The DestinationDetailsComponent can read the value and query details.

Mandatory route parameters

Traditionally web applications shared data between two pages in the URL.

Sections of the URL might define data required for the route. In the above example (/destination/:city), when the user navigates from list screen, the selected city is passed on as a route parameter. Imagine that the user selected Hyderabad. The URL /destination/:city translates to /destination/Hyderabad. The URL has the city name, which is nothing but the data. Here, the data, city name is very much part of the URL pattern.

Now we will have to use the data provided in the URL. The Component can access the URL parameter to query details about a particular city. Consider the following code snippet.

// ... For brevity show only the lines of code relevant 
import { ActivatedRoute, ParamMap } from '@angular/router';

@Component({
  // ... For brevity show only the lines of code relevant 
})
export class DestinationDetailsComponent implements OnInit {

  private destination: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.destination = this.route.snapshot.params.city;
  }

}

The statement this.route.snapshot.params.city retrieves route parameter city’s value. In the example destination/Hyderabad, the value is Hyderabad.

Notice ActivatedRoute is imported and injected into the component. It refers to the route associated while loading the component. It has a field snapshot, which has details about the route at that point in time. It is of type ActivatedRouteSnapshot.

One more level down, the child field params are key value pairs of data. They have all the route parameters for the given URL. In the above example, city is the only route parameter. Hence, we can retrieve the value of the parameter with variable name provided in the route configuration (city).

You may use the city value to query details of the city and show it in the details screen. See Figure 2.

details-screen

Figure 2: Details Screen

Optional route parameters

In a different scenario, data could be optional. Just to tweak the example a little bit, imagine /destination would show the first city in the list. To show details of a specific city, provide city name in the URL. In this case, city will become an optional parameter. The prior approach with city name as a route parameter wouldn’t work because city name is part of the URL pattern.

If the city name is not provided, the pattern doesn’t match. Hence, we would add a new route configuration to support optional city name. For clarity, let’s name the new route destination2.

Note: It need not be given a different name destination2. We may continue to name it as destination and a different configuration object without the mandatory city name in the route.

{
    path: "destination2",
    component: DestinationDetailsComponent
  }

As a convention, in a URL, optional values are supplied with Query parameters (query strings). The URL could be /destination2?city=Hyderabad. Or it may just be /destination2, in which case we will show the first city details. City is not part of route configuration anymore.

The component can now retrieve city name from queryParameters field instead of params. Refer to the following line of code for city name from query parameters.

this.route.snapshot.queryParams.city;

Notice we are using the same component DestinationDetailsComponent for both the routes (destination and destination2). In the example, the component needs to handle retrieving details from the route param or a query param.

Consider the following code snippet:

// ... For brevity show only the lines of code relevant 
import { ActivatedRoute } from '@angular/router';

@Component({
// ... For brevity show only the lines of code relevant 
})
export class DestinationDetailsComponent implements OnInit {

  private destination: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    // Get city name from params or query params
    let cityName = this.route.snapshot.params.city || this.route.snapshot.queryParams.city;
    if(cityName){
      this.destination = cityName;
    }else{
      // Get first city name from the list.
    }
  }
}
Optional fragments in the URL

It is a common practice in URLs to use breakpoints or hash fragments. They qualify a section of the page or view.

Let’s further alter the destination details view in the code sample. When no optional parameter was specified instead of always showing the first value, we may use an optional segment to decide first or last.

Access the fragment values using the following code statement. On the snapshot object, retrieve value from the fragment (like param or queryParam):

let fragment = this.route.snapshot.fragment;

Subsequently, pick the last city from the list if the segment value is last. See the code snippet and Figure 3.

this.destination = cityObjects && _.isArray(cityObjects) && ((fragment && fragment === "last") ? cityObjects[cityObjects.length-1] : cityObjects[0] );

last-fragment-value

Figure 3: Show last city in the list when fragment value is last

The sample component will show the first city details (in the list), when no fragment or query string is provided.

url-wo-optional-param

Figure 4: URL without any optional params

Additional Route Configurations

So far, we have seen three route configurations, one for destination list screen and two more for destination details screen.

It’s common to have a default route, which will be used when there is no route specified (neither destinations nor destination/[city name]). Imagine we have decided to make the list screen as the default route when no value specified.

Consider the following configuration object

{
    path: "",
    redirectTo: "/destinations",
    pathMatch: "full"
  }

When path is an empty string, we redirect to /destinations (the list screen). The field pathMatch qualifies the configuration and redirects to the destinations route when the given path matches completely .

In this case, it’s an empty path.

Whenever there is no path after domain name, it defaults to destinations route.

We may use pathMatch value prefix. It would qualify the configuration when the given path includes path specified in the configuration. Consider the following snippet.

{
    path: "list",
    redirectTo: "/destinations",
    pathMatch: "prefix"
  }

It matches any path that includes list at the beginning of the route. Above configuration matches the path /list/cities. However, it doesn’t match if the list is not at the beginning. For example, 0 or /test/list/cities doesn’t match.

Please note for an empty string (as the path), which is default route for the application, pathMatch value should be full. If prefix is used, it will match every URL. It’s an empty string after all.

Router Outlet

Router-Outlet acts as a placeholder for the component at a given route.

Routing is applied on router-outlet.

In a typical application, a root component will contain the router-outlet. There could be other components on root component beyond the router outlet. They could be header, footer, navigational controls like side nav etc.

See Figure 5 which depicts an application skeleton. Notice the Destination List component next to left nav, between header and footer. Routing is applied here as the router-outlet is positioned here. As route changes, a component configured for the given route takes place of the router-outlet.

In this case, as the route is /destinations, list component is shown on the outlet.

angular-app-skeleton

Figure 5: Application Skeleton

See Figure 6 with details screen in place of router-outlet:

angular-router-outlet

Figure 6: Details screen on the router-outlet

Consider the following code snippet. It’s the root component (app.component) template (html file). Notice the highlighted router-outlet. As route changes, a component is rendered at this place.

<div style="background-color: beige; padding: 10px 10px 10px 10px; text-align: center">
    <strong> This is a placeholder for header. </strong>
</div>

<table style="min-height: 400px">
    <tr>
        <td style="background-color: lightblue;min-width: 150px; padding-left: 15px">
            <div > 
                <strong>Left Nav</strong> 
                <div>item 1</div>
                <div>item 2</div>

            </div>
        </td>
        <td style="padding-left: 50px; background-color: lightcyan; min-width: 800px;">
            <router-outlet></router-outlet>            
        </td>
    </tr>
</table>

<div style="background-color: beige; padding: 10px 10px 10px 10px;  text-align: center">
    <strong> This is a placeholder for footer. </strong>
</div>

Router Link

We have almost come to the end of Part 1 of this article just by describing the creation and configuration of routes which renders a view or a component based on a URL (route).

However, how do we link to a route in a SPA built with Angular?

We will use Router Link. It’s a directive part of @angular/router module.

Use router on an anchor or a button element. Consider the following code snippet:

<div> <a routerLink="/destinations">Home</a> </div>

Here the home link takes the user back to list screen. However, the route is static, so to create a dynamic link, consider using the directive as follows.

<a [routerLink]="['/destination', dest.name]"> <strong>{{dest.name}}</strong></a>

In this case, the directive is concatenating array of values to create a dynamic route. The dynamic value includes name of the city.

Following are more input attributes used often with Router Link.

Query Params:

To the queryParams input attribute, provide a JSON object representing key value pairs on the query string.

In the following example, the value will be the selected city. The anchor element with the routerLink directive creates a link, clicking on which the user navigates to a route shown in Figure 7.

<a [routerLink]="['/destination']" [queryParams]="{cityName: dest.name}" > <strong>{{dest.name}}</strong></a>

router-query-params

Figure 7: Query string created based on input attribute queryParams

We can pass multiple key value pairs in the object for multiple query strings. The following code snippet creates a link, clicking on which, the target route will have multiple query string separated by ampersand (&). See Figure 8 for the target route.

<a [routerLink]="['/destination']" [queryParams]="{debug: true, cityName: dest.name}" > <strong>{{dest.name}}</strong></a>

multiple-query-strings

Figure 8: Multiple query strings

Fragments

Pass fragments in the link with fragment input attribute. Consider the following code snippet. See Figure 9 for the resultant route/URL.

<a [routerLink]="['/destination']" fragment="last" > <strong> Last >>> </strong></a>

fragment-append

Figure 9: Fragment appended

With this, we conclude Part 1.

The purpose of Part 1 was to accustom you with the basics of routing. Part 1 began by introducing routing, details configuring routes and briefly described creating links. Angular monorepo has included routing package and the module.

Take a break, or go through the concepts again since we will be needing them in the second part of this article.

Part II – Angular Routing – Advanced Scenarios

While the first part aimed as a getting started guide covering the basics of defining routes, Part 2 discusses some important features including asynchronously resolving a route after obtaining data, conditionally redirecting to an alternate route, authorization checks on a route etc.

Let us get started.

Base Element

This section describes a base element in index.html required for routing. It’s always there for an application created with Angular-CLI. If your application is a custom setup, it’s recommended to add this element.

A base element is required in index.html, with in the head tag. It defines how the relative path for the routing works. By default, it is set to a “/”. We can change it to a different path indicating that the router will now use the new base root (for relative path).

With the base element shown here, the router will use /qa-instance-1 in the URL. This is useful when your application is not deployed at the root. Let’s say, Index.html for the application maps to a domain-name.com/qa-instance-1. We can set the base URL so that all relative paths automatically use the base path.

<base href="/qa-instance-1">

base-href

Figure 10: Base href set to qa-isntance-1

Please note, the base element could also have a target attribute. In an example, if target value is set to __blank, all hyperlinks open in a separate window. It doesn’t affect the way Angular router works.

Location Strategy

This section compares retro style hash path in the URL with HTML5 style URL and routing. Angular router provides backward compatible hash path (#page1) in the route. By default, routes and URLs are seamless, for example my-domain-name.com/page1.

Yesteryear applications used URL in the link to decide,

i. Whether to reload the page or

ii. Go to a section in the page without invoking server reload. The URL may have hash path/bookmark (eg. #section2) in which case there will not be a server reload. Without hash path if the URL changes, page will reload.

Modern browsers support navigating to a URL without a reload and a hash path. It would just be another URL, that doesn’t reload from the server. In an example, navigation between my-domain-name.com/page1 and my-domain-name.com/page2 can happen without a server reload. There is no hash string required in the path.

Angular application provides both the options:

i. PathLocationStrategy – It is the default strategy with Angular router. In the code sample (My Travel), navigation between /destinations and /destination/city-name occurs without invoking a server reload.

ii. HashLocationStrategy – The hash strategy is not recommended. If server-side rendering is required, this strategy doesn’t integrate seamlessly. If you absolutely need this approach, provide the additional option useHash with a value true on the router module. Consider the following code snippet.

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

It prefixes every URL with a #. The URLs would be my-domain-name/#/destinations and my-domain-name/#/destination/city-name. See Figure-11.

hash-location

hash-location-strategy

Figure 11: URL with HashLocationStrategy

More details

URL changes contained in the browser are made possible using the following HTML5 API - pushState and replaceState on window.history object. However, Angular Router by default uses pushState API.

Yesteryear browsers considered every new URL as a resource on the server. If we are moving from my-domain.com/page1.html to page2.html, the browser would request for page2.html which physically existed on the server. In case it’s not a static html page, then ASP.NET or other similar server-side technologies created the content (or page2) on the fly.

pushState API allows changing the location on the window object without actually verifying the resource page2.html exists. window.history and state information are updated. It allows SPAs (single page applications) to navigate routes without server-reload.

Learn more about the HTML API at the following MDN Web Docs - https://developer.mozilla.org/en-US/docs/Web/API/History_API

Enable tracing of router events and data

We can enable tracing during navigation for debugging purposes. The Router events raised in the process are logged to the browser console.

Enable tracing by adding the flag while bootstrapping the router module. Consider following code snippet and Figure 12.

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

angular-router-events

Figure 12: Router events captured with tracing

Router Events

Here’s the complete list of Router Events. (reference: Angular documentation- https://angular.io/guide/router#router-events )

NavigationStart - This event occurs when navigation to the given route starts.

NavigationEnd - This event occurs when navigation to the given route ends.

NavigationCancel - An event occurs when navigation is cancelled. This could be due to Route Guard or resolve returning false during navigation.

NavigationError - An event occurs when navigation fails with an exception.

RouteConfigLoadStart - This event occurs before route configuration is lazy loaded by the router.

RouteConfigLoadEnd - This event occurs after route configuration is lazy loaded by the router.

RoutesRecognized - This event occurs when router correctly parses and recognized route configuration.

GuardsCheckStart - Route Guards are used for authorization check. This event occurs at the beginning of guard check.

GuardsCheckEnd - Route Guards are used for authorization check. This event occurs at the end of guard check.

ChildActivationStart - This even occurs before activating child route.

ChildActivationEnd - This even occurs after activating child route.

ActivationStart - This event occurs before activating/invoking a route.

ActivationEnd - This event occurs before activating/invoking a route.

ResolveStart - This event occurs at the beginning of resolving route configuration.

ResolveEnd - This event occurs at the end of resolving route configuration.

Please note, these events can be subscribed to, and we can perform additional action when the event occurs. Consider the following code snippet in ngOnInit of a component. router is an instance of @angular/router/Router.

this.router.events.subscribe( 
(event) =>
console.log("Router event captured", event));

Here, event is an instance of any one of the above event classes. For example, it could be an instance of NavigationStart. All the above classes/events are inherited from RouterEvents class.

Static and Dynamic data to the component at route

This section describes passing static or dynamic data to the component at a given route. This data could be used in the component after the route is resolved. In a static data example, route configuration provides page title. In a dynamic data example, route configuration supplies an observable, resolving city details. Component just shows the provided city information.

Routing configuration can include additional data to be sent to the component. To demonstrate this functionality, consider the following example.

To the city details route, let’s pass additional static data, window title. The Component can read this information and set the title.

const routes: Routes = [{
    path: "destination/:city",
    component: DestinationDetailsComponent,
    data: { title: "My City Details"}
  },…] // Routes configuration

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

The Component has this data available at this.route.snapshot.data. We will use the Title service imported from '@angular/platform-browser'. Here’s the code snippet.

constructor(private route: ActivatedRoute,
                private titleService: Title // Title Service injected
…) { }

  ngOnInit() {
    // Set title
    this.titleService.setTitle(this.route.snapshot.data.title || "");

Notice Figure 13 below. The Window title is set with static data sent from route configuration.

set-window-title

Figure 13: Window title set by the component

Fetch data on the fly before loading the route

While the data being passed to the route in the earlier example was static, we can obtain data on the fly.

This can be done asynchronously, via an observable or a promise, and will load the route only when resolved. (It may also return a dynamic value synchronously or instantly. If it’s a time-consuming operation, preferably make it an observable or a promise).

We can write code to not redirect to the route when the data is not available. We may also redirect to a different route. Some prefer to fallback to an error page.

In the code sample we will see shortly, we will redirect to the list screen if a particular city data is unavailable.

1. As a first step we will create a new service that will fetch the data on the fly, before loading the route. If you are using Angular CLI, run the following command to create the new service.

ng generate service providers/destination-resolver

This will create a DestinationResolverService in providers folder.

2. Consider implementing Resolve interface in @angular/router. While it’s not mandatory, it’s recommended. All that the Angular route configuration needs is the resolve function on the service. However, this interface ensures that the correct method signature is maintained. An incorrect function signature returns as an error at the build time.

Here’s the code snippet.

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { DataAccessService } from './data-access.service';
import { EMPTY, of } from 'rxjs';
import { take, mergeMap } from 'rxjs/operators';
import * as _ from 'lodash';

@Injectable({
  providedIn: 'root'
})
export class DestinationResolverService implements Resolve<any> {

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.dataAccess.queryDestinationByCity(route.params.city).pipe(
      take(1),
      mergeMap(city => {
        if (city && _.isArray(city) && city.length > 0) {
          return of(city); // returns observable based on the given object.
        } else { // if city details are not available, redirect to list screen
          this.router.navigate(['/destinations']); 
          return EMPTY; // an empty observable
        }
      })
    );
    
  }

  constructor(private dataAccess: DataAccessService, private router: Router) { }
}

In the sample, the ActivateRouteSnapshot instance is used to get the selected city name from the URL (route parameter).

The resolve function queries city details with the help of dataAccess service and returns the resultant observable. Notice, if the city details are available, we return the resultant object, which could be used in the component. Otherwise, we redirect to /destinations route, which is a list screen.

3. Use the resolver service (we just created) in the route configuration.

const routes: Routes = [
  {
    path: "destination/:city",
    component: PredefinedDestinationDetailsComponent,
    resolve: {
      destinationDetails: DestinationResolverService
    }
  },
// ... for brevity removing remaining route configuration objects
];

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

Notice, the object passed to the component is referred to as destinationDetails as configured above.

4. Create a new component with Angular CLI to use the city details provided

ng generate component predefined-destination-details

5. The following code snippet reads dynamic data object obtained while resolving the route:

export class PredefinedDestinationDetailsComponent implements OnInit {

  constructor(private router: ActivatedRoute) { }

  destination: any;

  ngOnInit() {
    this.router.data
      .subscribe( (data: {destinationDetails: any}) => {
        this.destination = data.destinationDetails[0];
})
  }

}

This code sample obtains the object from the ActivatedRoute instance and sets it to a component level class variable. This object destination is used in the template file for showing city details. The code snippet below is for template details.

<div> <a routerLink="/destinations">Home</a> </div>
<strong>Welcome to {{destination && destination.name}}</strong>
<div>It's a city in {{destination && destination.country}}</div>
<div>Plan to visit in {{destination && destination.bestTime}}</div>

Auth Guard

This section describes authentication and authorization checks before redirecting to a route. Because they are asynchronous, they can be based on results from a server-side API call.

While the dynamic data with resolve retrieves data before navigating to the route, the same principle can be applied to authorization. Consider the following code snippet with CanActivate.

1. Create a new authorization service with Angular CLI

ng generate service authorization

2. Implement the CanActivate interface. The canActivate function will be invoked by router before activation. If the function returns true it will activate the route. When false, the service can redirect to an error page.

export class AuthorizationService implements CanActivate {

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>{
    // Perform authorization check
   // ...
    // End authorization check
     if(authorizationCheck){
        return of(true);
     }else{
this.router.navigate(['/error']);
return of(false);
       }
  }

  constructor(private router: Router ) { }
}

Note that the canActivate function can asynchronously return an observable<boolean> (like above code snippet), promise<boolean> or a synchronous Boolean value.

3. Configure the router to use an authorization check

{
    path: "destinations",
    component: DestinationListComponent,
    canActivate: [AuthorizationService]
  },

Here are additional configuration parameters for authorization check.

CanActivateChild: Authorization check to activate child routes. Use authorization service that implements CanActivateChild interface. When the canActivateChild function resolves to true, it will activate child routes.

CanDeactivateChild: Validation to discard changes at a route.

ActivatedRoute Vs ActivatedRouteSnapshot

In earlier examples, in a component as a route, when we wanted to read information from the URL or a route (be it route params, query string or fragment) we used activatedRouteInstance.snapshot.

Here, the snapshot is an object of ActivatedRouteSnapshot. It provides data in the URL at that point in time. It’s an immutable object, which means every time we read snapshot information with a different route param, a new object is created. Also, we shouldn’t inject ActivatedRouteSnapshot as the immutable instance value doesn’t change.

There is another way to implement the same. Use observables on ActivateRoute instance, which changes over time. Consider following code snippet

import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';

Import ParamMap for accessing route params. Import switchMap for iterating values returned by Observable.

In the snippet shown below, instead of using snaptshot on this.route (instance of ActivatedRoute) use paramMap, which returns an Observable<ParamMap>. Pipe the result with iterator, switchMap. Access the route parameter city in the switchMap implementation.

this.route.paramMap.pipe(
      switchMap((params: ParamMap) => this.dataAccess
            .getDestinationDetails(params.get('city')))
      )

Conclusion

Routing is an important aspect of building a SPA (Single Page Application). All modern frameworks, be it Angular, React or Vue have a library for managing routing. Angular mono-repo comes with a routing module as well. It is well-rounded and provides all required features for taking care of navigation between views.

In this two-part tutorial on Angular Routing, we began with instructions to setup routing, then describe route configuration, an outlet where routing is applied and the links that are source of navigation.

In the second part, we got into the nitty-gritty details on features that control composing the URL (location strategy), additional optional configurations like the flag that enables tracing routing events. The tutorial then discussed how to conditionally control transition to a route based on an API response, be it the data for the component or authentication/authorization check.

References:

Angular documentation on routing- https://angular.io/guide/router

MDN Web Docs - https://developer.mozilla.org/en-US/docs/Web/API/History_API

Blog: Understanding Router State- https://vsavkin.com/angular-router-understanding-router-state-7b5b95a12eab

RouterEvent documentation - https://angular.io/api/router/RouterEvent

Thanks to Ravi Kiran for reviewing the article technically.

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
V Keerti Kotaru is an author and a blogger. He has authored two books on Angular and Material Design. He was a Microsoft MVP (2016-2019) and a frequent contributor to the developer community. Subscribe to V Keerti Kotaru's thoughts at his Twitter account. Checkout his past blogs, books and contributions at kvkirthy.github.io/showcase.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!