Angular2 relative route navigate in a guard

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;
    }
}

If you want to do it in a Resolve<T> method you can do it like this.

I think the same should apply for any kind of guard - using pathFromRoot.

This example removes the last 'segment' (that's the -1) and adds "XXXXXXXX".

resolve(route: ActivatedRouteSnapshot) {

   const fullPathFromRoot = route.pathFromRoot.reduce<string[]>((arr, item) => ([...arr, ...item.url.map(segment => segment.path)]), []);
   const newUrl = [ ...fullPathFromRoot.slice(0, -1), "XXXXXXXX" ];

   this.router.navigate(newUrl);
}

route is the route given to you - don't try to inject ActivatedRoute because that won't work.


You should be able to get the url you were attempting to navigate to from the state.url, parse it by using the provided urlSerializer, then splice in the new urlSegment you want relative to what was matched by the guard.

export class TermsGuard implements CanActivate<SecretFormComponent>{
    constructor(private router: Router, private urlSerializer: UrlSerializer) { }
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        let routerStateUrlTree = this.urlSerializer.parse(state.url);
        let newSegment = new UrlSegment('terms', {});
        routerStateUrlTree.root.children.primary.segments.splice(-1, 1, newSegment);
        this.router.navigateByUrl(routerStateUrlTree);
        return false;
    }
} 

You said "I'd like to be able to navigate to ['./terms']" but based on your routes I think you meant ['../terms'], but if you did mean ['./terms']just change the second parameter of the splice to a 0 so it doesn't remove the last segment.