Angular: как вызвать router.navigate() относительно целевого маршрута в RouteGuard?

У меня есть проект, разработанный с помощью Angular 4. Мне нужно контролировать доступ к определенному маршруту на основе прав пользователя. Упрощенная конфигурация маршрута выглядит так:

[
    { path: '', redirectTo: '/myApp/home(secondary:xyz)', pathMatch: 'full' },
    { path: 'myApp'
      children: [
        { path: '', redirectTo: 'home', pathMatch: 'full' },
        { path: 'home', ... },
        ...
        { path: 'product'
          children: [
            { path: '', redirectTo: 'categoryA', pathMatch: 'full' },
            { path: 'categoryA', component: CategoryAComponent, canActivate: [CategoryACanActivateGuard]},
            { path: 'categoryB', component: CategoryBComponent},
            ...
          ]
        },
        ...
      ]
    },
    ...
]

Теперь я хочу контролировать доступ к www.myWeb.com/myApp/product/categoryA. Если у пользователя недостаточно прав, он будет перенаправлен на ... /product/CategoryB. Для этого я написал CanActivate RouteGuard, класс Guard выглядит так:

import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, ActivatedRoute } from '@angular/router';
import { MyService } from '... my-service.service';

@Injectable()
export class CategoryACanActivateGuard implements CanActivate {
    constructor(private myService: MyService, private router: Router) { }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
        return this.myService.checkPermission()
            .then(result => {
                if (!result.hasAccess) {
                    //redirect here

                    this.router.navigate(['./myApp/product/categoryB']); 
                    //works, but I want to remove hardcoding 'myApp'

                    //this.router.navigate(['../../categoryB']);
                    //doesn't work, redirects to home page

                    //this.router.navigate(['./categoryB'], { relativeTo: this.route});
                    //do not have this.route object. Injecting Activated route in the constructor did not solve the problem

                }
                return result.hasAccess;
            });
    }
}

Все работает нормально, но я хочу перенаправить относительно целевого маршрута, как показано ниже:

this.router.navigate(['/product/categoryB'], { relativeTo: <route-of-categoryA>});
// or 
this.router.navigate(['/categoryB'], { relativeTo: <route-of-categoryA>});

К сожалению, relativeTo принимает только ActivatedRoute объектов, а у меня есть только ActivatedRouteSnapshot и RouterStateSnapshot. Есть ли способ навигации относительно целевого маршрута (в данном случае categoryA)? Любая помощь будет действительно оценена.

Примечание.

  • Я не могу изменить конфигурацию маршрута, кроме добавления некоторых охранников маршрута.
  • Я не ищу this.router.navigateByUrl, используя state.url. Я хочу использовать router.navigate([...], { relativeTo: this-is-what-need}).

person Faruq    schedule 12.04.2018    source источник


Ответы (2)


Оказывается, внедренный конструктор ActivatedRoute работает в RouteGuard иначе, чем в других местах, таких как Component.

В компоненте объект ActivatedRoute указывает на маршрут, который активировал этот компонент. Например, в классе CategoryAComponent следующий код перейдет к CategoryB:

this.router.navigate([ '../CategoryB' ], { relativeTo: this.route });

Но тот же код выше не будет работать в классе RouteGuard, добавленном в конфигурацию маршрута CategoryA. В моем тесте я обнаружил, что конструктор внедрил ActivatedRoute объектов, указывающих на корневой маршрут. С другой стороны, объект ActivatedRouteSnapshot (переданный в качестве параметра в функцию canActivate) указывает на целевой маршрут (в моем случае categoryA). Однако мы не можем передать этот объект ActivatedRouteSnapshot в функцию this.router.navigate(...).

Я не смог найти лучшего способа решить эту проблему, но у меня работает следующий код:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
    return this.myService.checkPermission()
        .then(result => {
            if (!result.hasAccess) {
                //redirect here

                let redirectTo = route.pathFromRoot
                    .filter(p => p !== route && p.url !== null && p.url.length > 0)
                    .reduce((arr, p) => arr.concat(p.url.map(u => u.path)), new Array<string>());

                this.router.navigate(redirectTo.concat('categoryB'), { relativeTo: this.route });
            }
            return result.hasAccess;
        });
}
person Faruq    schedule 13.04.2018
comment
Был такой же вопрос. Это решение громоздкое, но оно работает! Спасибо. - person johey; 28.10.2020

вы можете использовать относительную навигацию таким образом, если вы находитесь в /product/CategoryA и хотите перейти к /product/CategoryB:

this.router.navigate([ '../CategoryB' ], { relativeTo: this.route });
person Fateh Mohamed    schedule 12.04.2018
comment
Спасибо за ответ. Однако это не то решение, которое я ищу. state.url дает мне строку типа /myApp/product/categoryA(secondary:xyz). Мне нужно выполнить обработку строк, чтобы заменить categoryA на categoryB, а затем использовать router.navigateByUrl(), чего я пытаюсь избежать. - person Faruq; 13.04.2018
comment
попробуйте это: this.router.navigate(['../../categoryB']); для относительной навигации вы можете вернуться на 2 уровня и перейти - person Fateh Mohamed; 13.04.2018
comment
Я попробовал this.router.navigate(['../../categoryB']);, но он не может найти соответствующий маршрут и перенаправляет на домашнюю страницу. - person Faruq; 13.04.2018
comment
используйте это, я обновил ответ this.router.navigate([ '../CategoryB'], {relativeTo: this.route}); - person Fateh Mohamed; 13.04.2018
comment
В вашем обновленном ответе, как я могу найти this.route? Я нахожусь в функции canActivate, и внедрение ActivatedRoute по какой-то причине не работает. Получение такого объекта решит мою проблему - person Faruq; 13.04.2018
comment
добавьте этот частный маршрут: ActivatedRoute в класс конструктора - person Fateh Mohamed; 13.04.2018
comment
Внедрение AcivatedRoute в конструктор не помогает. Тот же код работает в классе компонентов, но не в классе RouteGuard. Я что-то упускаю? - person Faruq; 13.04.2018
comment
но я вижу, что у вас есть маршрут с таким же именем здесь canActivate (маршрут: ActivatedRouteSnapshot, попробуйте добавить его, используя другое имя в конструкторе - person Fateh Mohamed; 13.04.2018
comment
Они не одинаковы. Доступ к одному осуществляется с помощью route, а к другому - с помощью this.route. - person Faruq; 13.04.2018