Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save/recover router state in Angular

When user reaches a route in my application, which is protected by special guard, I'm redirecting him to a sign-in route, in order to perform authentication.

However, after successful authentication I wan't to redirect her back to a route she was initially reaching.

How do I preserve intermediate routing state in Angular and then redirect to it?

By routing state I mean the following:

  • The route itself, i.e. path
  • All query-string parameters
  • A hash string
like image 671
Slava Fomin II Avatar asked Oct 17 '22 10:10

Slava Fomin II


1 Answers

After playing with the router a bit, I've finally managed to implement the solution I wanted. I'm going to share it here with you.

So here's the implementation of IntermediateNavigationService:

import {Subscription} from 'rxjs/Subscription';
import {NavigationCancel, NavigationEnd, Router} from '@angular/router';
import {Injectable} from '@angular/core';


@Injectable()
export class IntermediateNavigationService {

  private savedUrl: string;
  private routerSubscription: Subscription;


  constructor (private router: Router) {
  }


  startWatch () {
    this.routerSubscription = this.router.events
      .subscribe(event => {
        if (event instanceof NavigationCancel) {
          // Saving URL from canceled route.
          this.saveUrl(event.url);
        } else if (event instanceof NavigationEnd) {
          // If user navigates away from sign-in page, clearing saved URL.
          if (!event.url.match(/^\/sign-in/)) {
            if (this.hasSavedUrl()) {
              this.clearSavedUrl();
            }
          }
        }
      })
    ;
  }

  stopWatch () {
    this.routerSubscription.unsubscribe();
  }

  saveUrl (url: string) {
    this.savedUrl = url;
  }

  hasSavedUrl () {
    return !!this.savedUrl;
  }

  getSavedUrl () {
    return this.savedUrl;
  }

  clearSavedUrl () {
    this.savedUrl = null;
  }

  goToSavedUrl (): Promise<boolean> {
    return this.router.navigateByUrl(this.savedUrl).then(result => {
      if (result) {
        this.clearSavedUrl();
      }
      return result;
    });
  }

}

The idea behind this service is pretty simple. When activated by startWatch() method (you should do it as early as possible in application lifecycle, for example in AppComponent constructor), it starts to monitor router events specifically filtering the NavigationCancel and NavigationEnd events.

  • When NavigationCancel happens, it means, that our authentication route guard prevented the navigation to a target route. Therefore we are saving the skipped URL inside of the service private variable.

  • When NavigationEnd happens, we check if user navigated away from our sign-in route. In this case we are no longer interested in preserving the saved URL, so we are clearing it. Otherwise it could lead to strange behavior, when user is redirected to URL he is no longer interested in.

After successful sign-in, we should just issue this code:

if (this.intermediateNavigationService.hasSavedUrl()) {
  this.intermediateNavigationService.goToSavedUrl();
} else {
  this.navigationService.redirectAfterSignIn();
}

Is short: we just checking if there is a saved URL, then we navigating to it, if not redirecting to the default one.

And if you are no longer interested in monitoring the state at some point, just call to stopWatch() in order to cancel the router events subscription. It could be done after successful sign-in actually to preserve resources (if you are not going to use this service for similar behavior which is not related to authentication of course).

And there is no need to call startWatch() if user is already authenticated.

Here's how your AppComponent could look like:

import {Component as NgComponent} from '@angular/core';

import {AuthenticationStateService} from './authentication/authentication-state.service';
import {IntermediateNavigationService} from './services/intermediate-navigation.service';


@NgComponent({})
export class MyAppComponent {

  constructor (
    private authenticationStateService: AuthenticationStateService,
    private intermediateNavigationService: IntermediateNavigationService
  ) {

    // Enabling intermediate URL handling.
    if (!this.authenticationStateService.isAuthenticated()) {
      this.intermediateNavigationService.startWatch();
    }

    // Listening for authentication event.
    this.authenticationStateService.onAuthenticated().subscribe(() => {
      this.intermediateNavigationService.stopWatch()
    });

  }

}

I hope it will help someone.

like image 181
Slava Fomin II Avatar answered Oct 20 '22 04:10

Slava Fomin II