[Angular] Angular 9. Перезапуск guard-ов текущей страницы. Trigger current route guards
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Столкнулся с необходимостью перезапустить guard-ы для текущей страницы, вне зависимости от того какая страница открыта.
Стандартного решения не нашел, а предлагаемые в интернете ограничиваются одной страницей. Поэтому написал своё и решил им поделиться.
Описание кейса
Страницы приложения разделяются на 3 группы:
- Только для авторизованных пользователей
- Только для неавторизованных пользователей
- Для любых пользователей
Авторизоваться или выйти можно на любой странице.
Если вход/выход производится на странице с ограниченным доступом, то нужно перейти на разрешенную страницу.
Если на странице без ограничений, то нужно остаться на текущей странице.
Для лучшего понимания желательно знать о:
Решение
Разграничение прав доступа к страницам осуществляется через guard — CanActivate. Проверка осуществляется при перехеде на страницу, но не реагирует на изменение прав, когда переход уже осуществлен. Во избежании дублирования логики по правам доступа, принудительно перезапускаем guard-ы.
Перезапуска guard-ов для текущей страницы осуществляется за счет навигации по текущему url. И изменения стратегий Router.onSameUrlNavigation и Route.runGuardsAndResolvers.
Здесь готовое решение. Более детально в следующем разделе.
SPL
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 на чувствительную к навигации на текущий url
const 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 outlet
const 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 и его предков на чувствительную к навигации на текущий url
const 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
===========
Похожие новости:
- [Angular, Open source, Rust, Визуализация данных, Отладка] Обновления в Chipmunk
- [Разработка веб-сайтов, Микросервисы] Как мы распилили монолит. Часть 1
- [.NET, Angular, DevOps, Искусственный интеллект, Data Engineering] Data Science vs AI: All You Need To Know
- [Разработка веб-сайтов, JavaScript, Клиентская оптимизация, Angular, Разработка под e-commerce] Дружим Angular с Google
- [Программирование, Angular, TypeScript] i18n в Angular
- [JavaScript, Angular, TypeScript] Что можно положить в механизм Dependency Injection в Angular?
- [Angular, TypeScript] Как управлять состоянием в Angular по мере роста приложения
- В ядро NetBSD добавлена поддержка VPN WireGuard
- [*nix, Сетевые технологии, Компьютерное железо, Сетевое оборудование] В RouterOS 7 добавили поддержку WireGuard
- [*nix] VPN в домашнюю локалку
Теги для поиска: #_angular, #_angular, #_guard, #_route, #_router, #_onsameurlnavigation, #_runguardsandresolvers, #_angular
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 16-Ноя 12:49
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Столкнулся с необходимостью перезапустить guard-ы для текущей страницы, вне зависимости от того какая страница открыта. Стандартного решения не нашел, а предлагаемые в интернете ограничиваются одной страницей. Поэтому написал своё и решил им поделиться. Описание кейса Страницы приложения разделяются на 3 группы:
Авторизоваться или выйти можно на любой странице. Если вход/выход производится на странице с ограниченным доступом, то нужно перейти на разрешенную страницу. Если на странице без ограничений, то нужно остаться на текущей странице. Для лучшего понимания желательно знать о: Решение Разграничение прав доступа к страницам осуществляется через guard — CanActivate. Проверка осуществляется при перехеде на страницу, но не реагирует на изменение прав, когда переход уже осуществлен. Во избежании дублирования логики по правам доступа, принудительно перезапускаем guard-ы. Перезапуска guard-ов для текущей страницы осуществляется за счет навигации по текущему url. И изменения стратегий Router.onSameUrlNavigation и Route.runGuardsAndResolvers. Здесь готовое решение. Более детально в следующем разделе.SPLimport { 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 на чувствительную к навигации на текущий url
const 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 outlet
const 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 и его предков на чувствительную к навигации на текущий url
const 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 =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 16-Ноя 12:49
Часовой пояс: UTC + 5