Русский
Русский
English
Статистика
Реклама

Из песочницы Angular 9. Перезапуск guard-ов текущей страницы. Trigger current route guards

Столкнулся с необходимостью перезапустить guard-ы для текущей страницы, вне зависимости от того какая страница открыта.

Стандартного решения не нашел, а предлагаемые в интернете ограничиваются одной страницей. Поэтому написал своё и решил им поделиться.

Описание кейса


Страницы приложения разделяются на 3 группы:

  • Только для авторизованных пользователей
  • Только для неавторизованных пользователей
  • Для любых пользователей

Авторизоваться или выйти можно на любой странице.

Если вход/выход производится на странице с ограниченным доступом, то нужно перейти на разрешенную страницу.

Если на странице без ограничений, то нужно остаться на текущей странице.

Для лучшего понимания желательно знать о:


Решение


Разграничение прав доступа к страницам осуществляется через guard CanActivate. Проверка осуществляется при перехеде на страницу, но не реагирует на изменение прав, когда переход уже осуществлен. Во избежании дублирования логики по правам доступа, принудительно перезапускаем guard-ы.

Перезапуска guard-ов для текущей страницы осуществляется за счет навигации по текущему url. И изменения стратегий Router.onSameUrlNavigation и Route.runGuardsAndResolvers.

Здесь готовое решение. Более детально в следующем разделе.
  import { Injectable } from '@angular/core';  import { ActivatedRoute, PRIMARY_OUTLET, Router, RunGuardsAndResolvers } from '@angular/router';  @Injectable()  export class GuardControlService {    constructor(      private route: ActivatedRoute,      private router: Router,    ) {}    /**    * Принудительный запуск guard-ов текущего url    */    forceRunCurrentGuards(): void {      // Изменяем стратегию Router.onSameUrlNavigation на чувствительную к навигации на текущий url      const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');      // Получаем текущий ActivatedRoute для primary outlet      const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);      // Изменяем стратегию runGuardsAndResolvers для ActivatedRoute и его предков на чувствительную к навигации на текущий url      const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');      // Запуск события навигации      this.router.navigateByUrl(        this.router.url      ).then(() => {        // Восстановление onSameUrlNavigation        restoreSameUrl();        // Восстановление runGuardsAndResolvers        restoreRunGuards();      });    }    /**    * Изменение onSameUrlNavigation с сохранением текущего значения    * @param router - Router, для которого осуществляется замена    * @param strategy - новая стратегия    * @return callback для восстановления значения    */    private changeSameUrlStrategy(router: Router, strategy: 'reload' | 'ignore'): () => void {      const onSameUrlNavigation = router.onSameUrlNavigation;      router.onSameUrlNavigation = strategy;      return () => {        router.onSameUrlNavigation = onSameUrlNavigation;      }    }    /**    * Получение последнего route для outlet-а    * @param route - Route относительно которого осуществляется поиск    * @param outlet - имя outlet-а, по которому осуществляется поиск    * @return Текущий ActivatedRoute для заданного outlet    */    private getLastRouteForOutlet(route: ActivatedRoute, outlet: string): ActivatedRoute {      if (route.children?.length) {        return this.getLastRouteForOutlet(          route.children.find(item => item.outlet === outlet),          outlet        );      } else {        return route;      }    }    /**    * Изменение runGuardsAndResolvers для ActivatedRoute и его предков, с сохранением текущих значений    * @param route - ActivatedRoute для которого осуществляется замена    * @param strategy - новая стратегия    * @return callback для восстановления значения    */    private changeRunGuardStrategies(route: ActivatedRoute, strategy: RunGuardsAndResolvers): () => void {      const routeConfigs = route.pathFromRoot        .map(item => {          if (item.routeConfig) {            const runGuardsAndResolvers = item.routeConfig.runGuardsAndResolvers;            item.routeConfig.runGuardsAndResolvers = strategy;              return runGuardsAndResolvers;              } else {            return null;          }        });      return () => {        route.pathFromRoot          .forEach((item, index) => {            if (item.routeConfig) {              item.routeConfig.runGuardsAndResolvers = routeConfigs[index];            }          });      }    }  }  


Дополнительное описание решения


Первое, что хочется попробовать для перезапуска guard-ов использовать навигацию по текущему url.

this.router.navigateByUrl(this.router.url);

Но, по умолчанию, событие перехода по текущему url игнорируется, и ничего не происходит. Чтобы это сработало нужно произвести настройку маршрутизации.

Настройка маршрутизации


1. Изменить стратегию Router.onSameUrlNavigation


onSameUrlNavigation может принимать следующие значения:

onSameUrlNavigation: 'reload' | 'ignore';

Для чувствительности к переходу по текущему url нужно установить 'reload'.

Изменение стратегии не осуществляет перезагрузку, но создает дополнительное событие навигации. Его можно получить через подписку:

this.router.events.subscribe();

2. Изменить стратегию Route.runGuardsAndResolvers


runGuardsAndResolvers может принимать следующие значения:

type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always' | ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean);

Для чувствительности к переходу по текущему url нужно установить 'always'.

Настройка маршрутизации во время конфигурации приложения


onSameUrlNavigation:

const routes: : Route[] = [];@NgModule({  imports: [    RouterModule.forRoot(      routes,      { onSameUrlNavigation: 'reload' }    )  ]})

runGuardsAndResolvers:

const routes: Route[] = [  {    path: '',    component: AppComponent,    runGuardsAndResolvers: 'always',  }];

Настройка маршрутизации во время исполнения


constructor(  private router: Router,  private route: ActivatedRoute) {  this.router.onSameUrlNavigation = 'reload';  this.route.routeConfig.runGuardsAndResolvers = 'always';}

Перезапуск guard-ов


Для перезапуска guard-ов одной определенной страницы достаточно настроить маршрутизацию во время конфигурации.

Но для перезапука guard-ов любой страницы, изменения runGuardsAndResolvers в каждом Route приведут к лишним проверкам. А необходимость всегда помнить об этом параметре к ошибкам.

Так как наш кейс предполагает перезапуск для любой страницы без ограничений в настройке приложения, нужно:

1. Заменить onSameUrlNavigation и сохранить текущее значение


// Изменяем стратегию Router.onSameUrlNavigation на чувствительную к навигации на текущий urlconst restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');.../*** Изменение onSameUrlNavigation с сохранением текущего значения* @param router - Router, для которого осуществляется замена* @param strategy - новая стратегия* @return callback для восстановления значения*/private changeSameUrlStrategy(router: Router, strategy: 'reload' | 'ignore'): () => void {  const onSameUrlNavigation = router.onSameUrlNavigation;  router.onSameUrlNavigation = strategy;  return () => {    router.onSameUrlNavigation = onSameUrlNavigation;  }}

2. Получить ActivatedRoute для текущего url


Так как inject ActivatedRoute осуществляется в сервисе, полученниый ActivatedRoute не связан с текущим url.

ActivatedRoute для текущего url лежит в последнем primary outlet и его нужно найти:

// Получаем текущий ActivatedRoute для primary outletconst primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);.../*** Получение последнего route для outlet-а* @param route - Route относительно которого осуществляется поиск* @param outlet - имя outlet-а, по которому осуществляется поиск* @return Текущий ActivatedRoute для заданного outlet*/private getLastRouteForOutlet(route: ActivatedRoute, outlet: string): ActivatedRoute {  if (route.children?.length) {    return this.getLastRouteForOutlet(      route.children.find(item => item.outlet === outlet),      outlet   );  } else {    return route;  }}

3. Заменить runGuardsAndResolvers для всех ActivatedRoute и его предков, с сохранение текущих значений


Guard, ограничивающий доступ, может располагаться в любом из предков текущего ActivatedRoute. Все предки располагаются в pathFromRoot.

// Изменяем стратегию runGuardsAndResolvers для ActivatedRoute и его предков на чувствительную к навигации на текущий urlconst restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');.../*** Изменение runGuardsAndResolvers для ActivatedRoute и его предков, с сохранением текущих значений* @param route - ActivatedRoute для которого осуществляется замена* @param strategy - новая стратегия* @return callback для восстановления значения*/private changeRunGuardStrategies(route: ActivatedRoute, strategy: RunGuardsAndResolvers): () => void {  const routeConfigs = route.pathFromRoot    .map(item => {      if (item.routeConfig) {        const runGuardsAndResolvers = item.routeConfig.runGuardsAndResolvers;        item.routeConfig.runGuardsAndResolvers = strategy;        return runGuardsAndResolvers;      } else {        return null;      }    });  return () => {    route.pathFromRoot      .forEach((item, index) => {        if (item.routeConfig) {          item.routeConfig.runGuardsAndResolvers = routeConfigs[index];        }      });  }}

4. Перейти по текущему url


this.router.navigateByUrl(this.router.url);

5. Вернуть runGuardsAndResolvers и onSameUrlNavigation в исходное состояние


restoreRunGuards();restoreSameUrl();

6. Объединить этапы в одной функции


constructor(  private route: ActivatedRoute,  private router: Router,) {}/*** Принудительный запуск guard-ов текущего url*/forceRunCurrentGuards(): void {  // Изменяем стратегию Router.onSameUrlNavigation на чувствительную к навигации на текущий url  const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');  // Получаем текущий ActivatedRoute для primary outlet  const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);  // Изменяем стратегию runGuardsAndResolvers для ActivatedRoute и его предков на чувствительную к навигации на текущий url  const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');  // Запуск события навигации  this.router.navigateByUrl(    this.router.url  ).then(() => {    // Восстановление onSameUrlNavigation    restoreSameUrl();    // Восстановление runGuardsAndResolvers    restoreRunGuards();  });}



Надеюсь, статья оказалась для Вас полезной. Если есть другие варианты решения, буду рад увидеть их в комментариях.
Источник: habr.com
К списку статей
Опубликовано: 05.09.2020 20:22:50
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Angular

Guard

Route

Router

Onsameurlnavigation

Runguardsandresolvers

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru