🚚 Управление типизированным маршрутом в приложении Angular
Проблема
Если вы работали с любым приложением Angular, созданным за последние несколько лет, вы, вероятно, видели код, который выглядит примерно так внутри приложения приложения или модуля маршрутизации:
Это конфигурация маршрутизации нашего приложения, и она используется, когда мы импортируем RouterModule в наш модуль:
Затем мы можем перейти к этим маршрутам, используя службу Router
в нашем компоненте:
Или мы можем использовать директиву routerLink
в нашем шаблоне:
Хотя это структура, которую Angular рекомендует в своей документации для настройки ваших маршрутов, ее основным недостатком является интенсивное использование жестко запрограммированных строк. Что произойдет, если (или, что более вероятно, когда) я напишу путь маршрута с ошибкой или решу изменить путь маршрута? Поскольку мы используем жестко заданные строки, Typescript не может сообщить нам, что значение, которое мы предоставили директиве routerLink
или функции navigateByUrl
, является недопустимым маршрутом. Конечно, мы можем использовать найти все и заменить в нашем редакторе кода, когда меняем маршруты, но полагаться на то, что другие или на себя в будущем не забудут это сделать, — не лучшая идея.
Первоначальное решение
Мы можем переместить все жестко заданные строки из нашего объекта маршрутов в отдельный класс с именем AppRoutesNames
. Мы сделаем каждый путь маршрута статическим свойством только для чтения, чтобы мы могли получить к нему доступ без необходимости инициализировать экземпляр класса.
Затем мы удалим жестко закодированные строки внутри нашего объекта маршрута и заменим их ссылками на пути маршрута в нашем классе AppRouteNames
.
После этого мы можем импортировать класс AppRouteNames
в наш компонент и использовать путь маршрута в качестве значения для функции navigateByUrl
.
Отлично! В нашей кодовой базе больше нет дублирующихся жестко запрограммированных строк. Однако у этого решения есть несколько проблем. Если мы хотим использовать свойства пути статического маршрута внутри HTML наших компонентов, нам нужно добавить класс AppRouteNames
в качестве защищенного свойства внутри нашего компонента:
Затем мы можем использовать класс AppRouteNames
внутри нашего шаблона:
Это работает, но добавление класса AppRouteNames
в качестве свойства к каждому компоненту, где мы хотим использовать пути маршрута в шаблоне, станет очень утомительным.
Предоставление класса AppRouteNames
Мы можем избавиться от утомительной работы по созданию свойства класса AppRouteNames
, добавив класс AppRouteNames
в качестве поставщика. Во-первых, мы удалим ключевое слово static
из всех свойств класса AppRouteNames
:
Далее мы добавим нового провайдера в массив провайдеров внутри нашего AppModule
для класса AppRouteNames
:
Теперь, когда мы удалили ключевое слово static из всех свойств пути маршрута в классе AppRouteNames
, нам также нужно изменить способ предоставления маршрутов для класса RouterModule
. Вместо того, чтобы передавать постоянную переменную Routes
, мы передадим пустой массив:
Затем мы изменим постоянную переменную Routes
на постоянную функцию с именем buildRoutes
, которая принимает наш класс AppRouteNames
и использует этот параметр для доступа к путям маршрута в нашей конфигурации маршрута:
Чтобы предоставить эту конфигурацию маршрута для RouterModule
, мы добавим еще одного провайдера ниже поставщика для AppRouteNames
в нашем AppModule
. Мы будем использовать токен внедрения ROUTES
, импортированный из @angular/router
, как вещь, которую мы предоставляем, использовать фабрику и нашу функцию buildRoutes
, чтобы вернуть конфигурацию маршрута, установить multi
в true и обязательно указать класс AppRouteNames
в качестве зависимости:
В результате теперь мы можем внедрить класс AppRouteNames
в качестве защищенного члена внутрь нашего компонента и использовать свойства его класса гораздо более традиционным способом. Вот как это выглядит в классе компонента:
А также то, как это теперь используется в шаблоне:
Больше не нужно добавлять класс AppRouteNames
в качестве свойства компонента. Довольно гладко!
Но подождите... Есть еще (проблемы)!
Помните, я сказал, что у нашего первоначального решения было несколько проблем? Вот что произойдет, если мы попытаемся перейти к маршруту FIRST_CHILD_COMPONENT
, который вложен под маршрутом THIRD_COMPONENT_WITH_CHILDREN
в нашей конфигурации маршрута, используя только свойство FIRST_CHILD_COMPONENT
.
Вместо того, чтобы перейти к нашему FirstChildComponent
, мы вместо этого перенаправляемся на наш резервный маршрут, что указывает на то, что мы передали нашему маршрутизатору недопустимый путь маршрута. Если мы вернемся к нашему классу AppRouteNames
и посмотрим на свойство FIRST_CHILD_COMPONENT
, мы увидим, что значение равно 'first-child-component'
. Хотя эта настройка могла работать для любого из наших маршрутов на первом уровне нашей конфигурации маршрута, всякий раз, когда нам нужно перейти к маршруту, вложенному в другой маршрут, нам нужно включить как путь родительского(ых) маршрута, так и путь маршрута для компонент, к которому мы пытаемся перейти. Мы можем временно решить эту проблему, соединив пути THIRD_COMPONENT_WITH_CHILDREN
и FIRST_CHILD_COMPONENT
route косой чертой:
Теперь эта ссылка успешно ведет на страницу FirstChildComponent
:
Но, боже мой, это решение уродливо. Что делать, если у нас есть более одного вложенного уровня в конфигурации маршрута? Нам просто нужно продолжать добавлять родительские пути вместе с косой чертой между ними. Повторение этой логики везде, где мы хотим использовать вложенный маршрут, нарушает принцип DRY и быстро устаревает.
Улучшение нашего класса AppRouteNames
Хотя не все пути маршрутов в нашем классе являются вложенными, лучше, если структура для всех наших маршрутов будет одинаковой. Первое, что мы сделаем, это переименуем все пути маршрута в нашем классе AppRouteNames
, добавив к ним префикс RELATIVE_
. Путь резервного маршрута не будет использоваться нигде, кроме нашей конфигурации маршрута, поэтому мы можем оставить его в покое.
Эти пути маршрута с префиксом RELATIVE_
— это пути маршрута, которые мы будем использовать в нашей конфигурации маршрута, которую мы передаем в RouterModule
внутри нашего AppModule
. Нам нужно будет изменить нашу конфигурацию маршрута, чтобы использовать эти новые относительные пути маршрута.
Для каждого пути маршрута, предназначенного для использования вне конфигурации маршрута, мы создадим другое свойство с тем же именем, но без префикса RELATIVE_
. Это свойство будет либо относительным путем маршрута, либо для вложенного компонента путем маршрута, построенным из ряда относительных путей.
Для наших вложенных путей маршрута мы можем улучшить это, добавив в наш класс AppRouteNames
приватную вспомогательную функцию, которая принимает сегменты URL и соединяет их косой чертой, поэтому нам не нужно помнить об этом самостоятельно.
Затем мы можем вернуться к тому месту, где мы изначально просто использовали путь маршрута FIRST_CHILD_COMPONENT
, и изменить его назад только на FIRST_CHILD_COMPONENT
.
Теперь это приводит нас к нашему вложенному компоненту правильно, без конкатенации строк внутри нашего шаблона!
Хотя это и решило нашу проблему с вложенными маршрутами, оно удвоило количество свойств в классе AppRouteNames
, которые могут складываться в более крупном приложении. Это решение также сталкивается с проблемами при обработке путей маршрута, содержащих параметры маршрута.
Обработка параметров маршрута
До сих пор мы не использовали никаких маршрутов, использующих преимущества параметров маршрута Angular. Мы можем начать с добавления относительного маршрута к нашему классу AppRouteNames
.
Затем мы можем добавить маршрут в нашу конфигурацию маршрута в функции buildRoutes
.
В этом случае мы не сможем использовать другое свойство класса для полного пути маршрута, потому что нам нужно иметь возможность динамически генерировать часть :id
. Вместо этого мы будем использовать функцию, которая принимает id
в виде строки. Мы по-прежнему можем использовать вспомогательную функцию buildURL
для построения URL-адреса, но из-за раздела :id
пути относительного маршрута мы не сможем использовать его внутри функции, и вместо этого нам придется дублировать часть 'fourth-component'
URL-адреса. путь маршрута.
Теперь мы можем использовать этот маршрут в нашем шаблоне:
Направляем нас к этому компоненту с идентификатором 1:
Но это возвращает нас к нашей первоначальной проблеме с дублирующимися строками, и хотя они не распределены по всей кодовой базе, кажется, что нам не нужно делать дублирующиеся строки на этом этапе. Что, если бы был способ получить доступ к нашим путям маршрута как к типизированному объекту и избежать дублирования строк, без необходимости указывать кучу путей маршрута и функций построения в отдельном классе, кроме нашей конфигурации маршрута?
ngx-продвинутый маршрутизатор
Именно этот вопрос привел меня к созданию ngx-advanced-router. Он работает как замена стандартного Angular RouterModule
и позволяет указать конфигурацию маршрутизации в качестве внедряемой службы и быстро и легко получить доступ к путям маршрутов. Мы можем начать с установки этого пакета в наш проект Angular:
npm install --save ngx-advanced-router
Далее нам нужно создать новый сервис, расширяющий AdvancedRouteService
. Расширение этого класса требует, чтобы мы реализовали одно абстрактное свойство: routesConfig
. В это свойство мы поместим аналогичную конфигурацию маршрута, которую мы вернули ранее в нашей функции buildRoutes
. Однако вместо массива объектов маршрута у нас будет объект с ключами и объектами маршрута в качестве значений. Вот как это будет выглядеть, если мы преобразуем конфигурацию маршрута из нашей функции buildRoutes
:
Следует отметить, что мы переместили фактические строки пути маршрута обратно в конфигурацию маршрута, точно так же, как они были у нас в самом начале этой статьи. Еще одна вещь, которую следует отметить, это то, что мы можем передать не только строку для пути, но и функцию, как показано на пути для маршрута fourthWithRouteParameter
.
Чтобы использовать эту службу, мы вернемся к нашему AppModule
и удалим провайдеров AppRouteNames
и ROUTES
из нашего массива провайдеров, а также RouterModule
из нашего массива импорта. Вместо этого мы добавим AdvancedRouterModule
с его функцией forRoot
в массив импорта, передав нашу недавно созданную службу маршрутизации:
В нашем AppComponent
, куда мы вводили класс AppRouteNames
, мы вместо этого импортируем класс AppRouteService
. Чтобы использовать эту службу для доступа к нашим путям маршрутов, мы будем использовать свойство службы routes
. Доступ к этому свойству дает нам хороший набор параметров автозаполнения на основе конфигурации маршрута, которую мы установили в службе ранее.
Если мы хотим перейти к «первому» маршруту, мы можем использовать свойство path
, которое возвращает полный путь маршрута.
Для маршрутов с дочерними элементами у них будет свойство children
в дополнение к свойству path
, что также дает нам хороший список автозаполнения.
Вот как выглядит полный код для доступа к этому дочернему маршруту:
Для динамических маршрутов, включающих параметры маршрута, таких как маршрут fourthWithRouteParameter
, объект маршрутов предоставляет нам функцию с теми же параметрами, что и функция, которую мы настроили в нашей конфигурации маршрута. Мы можем вызвать эту функцию, а затем получить доступ к свойству path
, чтобы получить полный путь маршрута.
Вот и все! Это решение решает все проблемы, которые у нас были:
- Нет повторяющихся строк
Теперь строки используются один раз в конфигурации маршрута, а свойствоroutes
используется для доступа к путям маршрута для всего остального. - Нет дополнительных свойств компонента
Наша служба маршрутизации использует внедрение зависимостей Angular, поэтому нет необходимости добавлять свойство к нашему компоненту только для доступа к нашим путям маршрута. - Никакого импровизированного построения путей маршрута
Служба маршрутизации позаботится о создании наших путей маршрута для нас, независимо от того, находятся ли они на корневом уровне или на 5 уровнях в глубину. - Отсутствуют относительные пути маршрутов
ngx-advanced-router обеспечивает маршруты кRouterModule
Angular в том формате, который он ищет, и предоставляет вам абсолютные пути маршрутов. - Нет дополнительного класса пути маршрута
Однажды объявите настройку маршрута в службе маршрутизации, используйте ее в функцииAdvancedRouterModule.forRoot()
, и все готово!
Дайте мне знать, что вы думаете в комментариях! Я открыт для предложений по улучшению этого пакета.