How to Navigate to Previous Page in Angular

Nils Mehlhorn - Oct 21 '20 - - Dev Community

Contents
Static Back Navigation with Routing
Dynamic Back Navigation with Browser History
Live Example

Sometimes we would like to offer users a way to navigate back to where they had been before. Generally, this is what the browser's back button is for, but we can also provide the same feature in other places. For example when there's a list of users linked to a detail view and you want to display some kind of back button to return to the list. Let's explore a couple different approaches - scroll to the end to see a working example with all of them.

This is how I'm setting up my routes for this example. Note that UserListComponent is supposed to contain a list of all users, while ActiveUsersComponent contains only some. Both components will link to UserDetailComponent from which we'd then like to navigate back.

const routes: Routes = [
  {
    path: 'users',
    component: UsersComponent,
    children: [
      { path: '', component: UserListComponent },
      { path: 'active', component: ActiveUsersComponent },
      { path: ':id', component: UserDetailComponent },
    ],
  },
  { path: '**', redirectTo: 'users' },
]
Enter fullscreen mode Exit fullscreen mode

Trying to build a table of users or other entities with Angular Material? Checkout my article on implementing a datasource for server-side paging, sorting and filtering.

Static Back Navigation with Routing

One solution would be defining a router link in the detail component and explicitly navigating back to the parent with an absolute route:

<a routerLink="/users">Back with Absolute Routing</a>
Enter fullscreen mode Exit fullscreen mode

Alternatively, you could also do this programmatically from the component class, but keep in mind that router links are more semantic than navigations triggered through click events.

import { Router } from '@angular/router'

@Component({...})
export class UserDetailComponent {
  constructor(private router: Router) {}

  back(): void {
    this.router.navigate('/users')
  }
}
Enter fullscreen mode Exit fullscreen mode

While this implementation is fine in general, it might get repetitive for multiple different child components. Also, it won't work in places where you don't really know the parent route, e.g. when you're displaying some kind of content header which always provides a back button.

Another solution involves relative routing. You might be familiar with relative routes from links pointing towards children, but they can also be used the other way around where two dots reference the parent route:

back(): void {
    this.router.navigate("..");
}
Enter fullscreen mode Exit fullscreen mode
<a routerLink="..">Back with Relative Routing</a>
Enter fullscreen mode Exit fullscreen mode

However, this will only work when the list component is registered as the child with an empty path like I've done in the route configuration above. Otherwise you'd have to append the dots with the child route that you're targeting (e.g. ../list). Basically, this approach just navigates one layer up in the the routing hierarchy.

Both absolute and relative routes won't necessarily go back to where the user has been before. They provide static navigation and it's already clear during development where the corresponding navigations will end up. Therefore, it's not easily possible to go back to /users/active even when this is where the user was before navigating to the detail view. We need to find another solution to facilitate this behavior.

Join my mailing list and follow me on Twitter @n_mehlhorn for more in-depth Angular knowledge

Dynamic Back Navigation with Browser History

The browser's back button is based on the browser history. Luckily, it has a JavaScript API which we can use to navigate dynamically back and forth through our Angular application. In fact, Angular even provides the Location service as a platform abstraction.

This service has a back() method which does exactly what we want: it navigates one step back in the browser's history. We can inject the service into the detail component or any intermediate component and call it upon a button click:

import { Location } from '@angular/common'

@Component({...})
export class UserDetailComponent {
  constructor(private location: Location) {}

  back(): void {
    this.location.back()
  }
}
Enter fullscreen mode Exit fullscreen mode

This solves the problem we had before and the user can now navigate back to the actual list he came from. You can try this in the example below:

  1. /users: Click on first user
  2. /users/1: Click on "back with location"
  3. /users: Works! Now click on "Active"
  4. /users/active: Click on the first user
  5. /users/1: Click on "back with location"
  6. /users/active: Also works!

Sadly, there's one edge case: if the application is started on the detail router after opening the browser or a new tab there won't be an entry in the history to go back to. In that case location.back() will throw the user out of your Angular app. There's also no API for directly inspecting the browser history as that might pose security issues, but there's still a way how we can fix this.

We'll create a service for wrapping the back navigation. There we'll also be listening to router events of type NavigationEnd to manage an app-specific navigation history. Now, if the history still contains entries after popping the current URL off of the stack, we can safely navigate back. Otherwise we're falling back to the application route:

import { Injectable } from '@angular/core'
import { Location } from '@angular/common'
import { Router, NavigationEnd } from '@angular/router'

@Injectable({ providedIn: 'root' })
export class NavigationService {
  private history: string[] = []

  constructor(private router: Router, private location: Location) {
    this.router.events.subscribe((event) => {
      if (event instanceof NavigationEnd) {
        this.history.push(event.urlAfterRedirects)
      }
    })
  }

  back(): void {
    this.history.pop()
    if (this.history.length > 0) {
      this.location.back()
    } else {
      this.router.navigateByUrl('/')
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We can then inject the NavigationService into components and call it's custom back() method instead of directly using Angular's Location service:

import { NavigationService } from './navigation.service'

@Component({...})
export class UserDetailComponent {
  constructor(private navigation: NavigationService) {}

  back(): void {
    this.navigation.back()
  }
}
Enter fullscreen mode Exit fullscreen mode

Additionally, we could wrap the existing solution in an Angular directive for easy re-use. Simply inject the NavigationService and call the back() method using a HostListener:

import { Directive, HostListener } from '@angular/core'
import { NavigationService } from './navigation.service'

@Directive({
  selector: '[backButton]',
})
export class BackButtonDirective {
  constructor(private navigation: NavigationService) {}

  @HostListener('click')
  onClick(): void {
    this.navigation.back()
  }
}
Enter fullscreen mode Exit fullscreen mode

Afterwards you can apply the directive in component templates like this:

<button backButton>Back with NavigationService</button>
Enter fullscreen mode Exit fullscreen mode

Live Example

Here's a StackBlitz showing examples for all approaches. If you've got any questions post a comment below or ping me on Twitter @n_mehlhorn. Also follow me there and join my mailing list to get notified when I'm posting something new.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .