Проблемы защиты роута в Angular

Проблемы защиты роута в Angular

От автора: Angular роутинг все еще не идеален. По крайней мере, это можно сказать про последнюю стабильную версию 4.3.6, о которой мы и поговорим в этой статье. Вы заметите это, когда попробуете прототипировать более сложную архитектуру роутинга. Вложенная структура будет полна resolve и canActivation, особенно при разрастании приложения. В этой статье я постараюсь пролить свет на сложности, с которыми я столкнулась при работе с Angular Router.

Ну а пока, начнем с основ. Представьте, что у нас есть страница с информацией о двух брендах машин: tesla и arrinera. Роутинг может быть таким:

Давайте зададим роутинг:

Довольно просто, правда? Нам нужна информация о машинах, давайте создадим CarsResolver в качестве провайдера данных:

И обновим объявление роута:

Теперь представьте, что нам нужно защитить наш роут :cid, чтобы он принимал лишь 2 значения: tesla и arrinera, передаваемых CarsResolver: Наша главная цель:

Просто, подумаете вы и скажите «давайте используем CanActivate». ОК, давайте попробуем. Он должен проверять, доступен ли провайдер по бренду автомобиля (:cid) в разрешенном родительском списке роутов cars. Если да, позволяем инициализировать компонент, если нет – запрещаем.

Установка CarGuard:

Довольно просто: получите доступ к данным cars, разрешенным CarsResolver на родительском роуте. Найдите в объекте списка марок автомобилей cid, предоставленный пользователем в URL, и разрешите его булево значение, которое показывает доступность авто.

Сохраните, запустите и бац! Не работает. route.parent.data.cars не определен. Почему? Потому что CarsResolve еще не запустился. Внутри Angular взывает CanActivate до процесса разрешения данных (resolve). То есть у нас нет доступа к разрешенным данным. Подход canActivate больше похож на: «Можно перейти по роуту? Если да, запусти резолв, потом инициализируй компонент». Что еще хуже, когда у нас 2 canActivate (один в дочернем роуте, другой в родительском), оба вызываются одновременно. Это известная проблема, и ее уже пофиксили в 5.0.0-beta.1. Но пока что забудьте про нее. Правда в том, что в определенным обстоятельствах мы не можем пользоваться преимуществами Angular Guards.

Нам нужен некий гибрид Resolve и canActivate. Resolve может достучаться до разрешенных данных родительского роута. Это огромное преимущество над canActivate. Guards, с другой стороны, могут предотвращать инициализацию компонентов, но как это сделать? Давайте покопаемся во фреймворке…

Когда заданный любым пользователем canActivate резолвит в false (см. здесь), вызывается функция canLoadFails. Эту функция вызывает другую общую функцию navigationCancelingError, которая генерирует ошибку Observable, который содержит интересный объект Error со свойством ngNavigationCancelingError, заданным в true. Такой хорошо сформированный Error вынуждает Angular не инициализировать компонент. Это нам и нужно для защиты роута! С помощью этого мы можем задать кастомный resolve, который будет вести себя одновременно как canActivate и resolve. Назовем его CarResolve.

Замените нерабочий canActivate на CarResolve:

И установите CarResolve как:

Как это работает? Так как это resolve, у нас есть доступ к разрешенным данным от родительского списка роута cars. Бросая объект со свойством ngNavigationCancelingError, мы ведем себя как canActivate. Это вынуждает Angular отказаться от инициализации компонента. Роут событие NavigationCancel создается и отсылается. Оно нам пригодится позже, а сейчас давайте протестируем наши роутинг переходы от источника к цели:

Магия для переходов состояний! Выше сказано, что начальное состояние уже активировано (/cars, /cars/tesla, /cars/arrinera), после чего мы пытаемся перейти на целевой роут. Переход может быть удачным или нет. Но что происходит, когда переход не удается? Вызывается функция resetUrlToCurrentUrlTree. Она лишь заменяет URL на адрес исходного роута. Когда состояние активировано (например, /cars), и мы пытаемся перейти по неверному роуту /cars/ford, после отмены перехода эта функция откатывает URL до последнего активного роута (/cars). С точки зрения пользователя ничего не изменится, мы останемся в исходном роуте.

Но что если у нас не активировано исходное состояние? А такое возможно? Да. Это можно сделать, открыв в браузере новую вкладку и перейдя по адресу //localhost:4200/cars/ford. Angular роутер пытается активировать состояние /cars/ford, и у него не получается… К сожалению, так как у нас нет исходного активного состояния, будет показана пустая страница. По крайней мере, корневой элемент полностью пустой. Не знаю, должно ли быть так, но эта проблема возникает также для canActivate.

Как обойти эту проблему? Просто следя за исходным URL после отмены активации роута. Давайте применим подписку к событию NavigationCancel:

Мы получили событие NavigationCancel, а свойство ActivatedRouteSnapshot url ведет на пустую строку, т.е. пользователь открыл urL (который ведет на неверное состояние приложения) в новой вкладке браузера. Мы можем перенаправлять пользователя на заданный роут, скрывая пустую страницу. Редирект можно поместить внутрь CarResolve, но мне кажется, resolve должен резолвить данные (и выбрасывать ошибки) без дополнительной логики редиректов.

Кликните по ссылке для перехода на plunker с примером приложения с описанными проблемами. Посмотрите код и обязательно кликните Launch the preview in a separate window, чтобы поиграться с возможностями роутинга в новой вкладке браузера. Посмотрите консоль.

Автор: Adam Płócieniak

Источник: //medium.com/

Редакция: Команда webformyself.

Метки:

Похожие статьи:

Комментарии Вконтакте: