Search code examples
angularangular2-routing

Angular2 relative route navigate in a guard


We have child routes definitions presented here, in which we use a guard to check if the user has accepted terms before using our service.

account/secret/secret.routes.ts:

import { Routes } from '@angular/router';
import { SecretFormComponent } from './secret-form.component';
import { SecretTermsComponent } from './secret-terms.component';
import { TermsGuard } from './services/terms-guard.service';

export const secretRoutes: Routes = [
  {
    path: '',
    redirectTo: 'form'
  },
  {
    path: 'form',
    component: SecretFormComponent,
    canActivate: [TermsGuard]
  },
  { path: 'terms', component: SecretTermsComponent }

  // otherwise redirect to form
  { path: '**',  redirectTo: 'form' }
];

In our terms-guard, we have defined this code:

this.router.navigate(['/account/secret/terms']);
return false;

Is there a way to redirect using relative route navigation from within a "routing group"? Because defining an absolute path may be break if one day our account website dashboard is renamed to anything else like my-account for example. We want our secret module to be reusable.

I'd like to be able to navigate to ['./terms'] in my guard but it doesn't work, just like the guard doesn't know "from where" to start relative navigation.


Solution

  • What you want to use is the RouterStateSnapshot parameter of the canActivate method of your guard.

    The RouterStateSnapshot has the current path you are in the process of routing to, whereas the Router.url is the current path your are routing from (which is why @Günter Zöchbauer's answer doesn't quite work in this scenario).

    For this to apply completely to your use case without additional code you will want to change your routes to

    /account/secret/form and /account/secret/form/terms

    import { Injectable } from '@angular/core';
    import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
    import { Observable } from 'rxjs/Observable';
    
    @Injectable()
    export class ChildRouteGuard implements CanActivate {
        constructor(private router: Router) { }
    
        canActivate(_route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
            // user can view the specific route
            if (...) {
                return true;
            }
    
            // else route them to a variation of the route
            this.router.navigate([state.url, 'terms']);
    
            return false;
        }
    }
    

    Edit 1

    You could keep your current url structure by also taking advantage of the ActivatedRouteSnapshot as seen below

    Assuming routes like account/secret/form and account/secret/terms

    route.url is an array of url segments, we want the last one because that is going to be form. We find the index of that in the current route we are trying to go to account/secret/form and slice it out. At this point we have the 'parent' of the current route we are trying to navigate to account/secret and we can add on the sibling to form which is terms.

    Note: this pattern breaks down when you have repeated occurrences of form in your url

    import { Injectable } from '@angular/core';
    import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
    import { Observable } from 'rxjs/Observable';
    
    @Injectable()
    export class ChildRouteGuard implements CanActivate {
        constructor(private router: Router) { }
    
        canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
            // user can view the specific route
            if (...) {
                return true;
            }
    
            let parentUrl = state.url
                .slice(0, state.url.indexOf(route.url[route.url.length - 1].path));
    
            this.router.navigate([parentUrl, 'terms']);
    
            return false;
        }
    }