Прочтите обновленную версию этой статьи на inDepth.dev

Угловой маршрутизатор - это чудо программной инженерии. Маршрутизатор Angular незаменим для большинства приложений - от управления навигацией по приложениям до обеспечения защиты маршрута и облегчения отложенной загрузки модулей. Однако для многих разработчиков внутренняя работа маршрутизатора остается загадкой. Эта серия статей призвана изменить это, давая разработчику более глубокое понимание маршрутизатора. В этой вводной статье будет представлен обзор архитектуры маршрутизатора, а также некоторые полезные мысленные модели для получения интуитивного представления о том, как работает маршрутизация в Angular. В оставшейся части этой серии мы более подробно рассмотрим каждую часть архитектуры и реализации маршрутизатора.

Более глубокое понимание маршрутизатора позволит вам настроить его в соответствии с вашими потребностями, если вам когда-либо понадобится изменить поведение по умолчанию. Кто знает? Знание архитектуры маршрутизатора может даже помочь вам решить сложные проблемы на работе.

Древо состояний

Приложение Angular - это дерево компонентов. Некоторые из этих компонентов, такие как корневой компонент, останутся на своих местах в течение всего приложения. Однако мы также хотим иметь возможность динамически отображать определенные компоненты, и один из способов добиться этого - использовать маршрутизатор. Используя модуль маршрутизатора вместе с директивами маршрутизатора-розетки, можно определить части нашего приложения, которые будут отображать различные наборы компонентов в зависимости от текущего URL-адреса. Например, в простом приложении для создания заметок вы можете захотеть отобразить домашний компонент для одного URL-адреса и список заметок для другого URL-адреса.

Внутри эти маршрутизируемые наборы компонентов известны как состояния маршрутизатора. Маршрутизатор будет моделировать маршрутизируемые компоненты в приложении в виде дерева состояний маршрутизатора. Продолжая приведенный выше пример , домашняя страница будет состоянием маршрутизатора, а компоненты для отображения списка заметок - другим состоянием маршрутизатора.

Основное внимание маршрутизатора уделяется обеспечению навигации между маршрутизируемыми компонентами в приложении Angular, что требует от маршрутизатора визуализации набора компонентов с использованием выхода на странице, а затем отражения состояния визуализации в URL-адресе. Для того, чтобы сделать это , маршрутизатору необходимо каким-то образом связать URL-адреса с соответствующим набором компонентов для загрузки. Это достигается за счет того, что разработчик может определить объект конфигурации состояния маршрутизатора, который описывает, какие компоненты отображать для данного URL-адреса.

Состояния маршрутизатора определяются в приложении путем импорта RouterModule и передачи массива объектов Route в его метод forRoot. Например, массив маршрутов для простого приложения может выглядеть так:

При передаче в routerModule.forRoot() он создаст следующее дерево состояний маршрутизатора:

Важным моментом является то, что в любое время некоторое состояние маршрутизатора (то есть расположение компонентов) отображается на экране для пользователя на основе текущего URL-адреса. Такое расположение известно как активный маршрут. Активный маршрут - это просто какое-то поддерево дерева всех состояний маршрутизатора. Например, URL-адрес /notes будет представлен как следующий активный маршрут:

Некоторые интересные моменты, касающиеся конфигурации маршрута:

  1. RouterModule имеет метод forChild, который также принимает массив маршрутов. Хотя оба forChild и forRoot возвращают модули, содержащие все директивы маршрутизатора и конфигурации маршрутов, forRoot также создает экземпляр службы маршрутизатора. Поскольку служба маршрутизатора изменяет местоположение браузера, которое является общим глобальным ресурсом, может быть только одна активная служба маршрутизатора. Вот почему вы должны использовать forRoot только один раз в своем приложении, в корневом модуле приложения. Модули функций должны использовать forChild.
  2. Когда путь маршрута совпадает, компоненты, указанные в свойствах component состояния маршрутизатора, отображаются с помощью router- розетки, которые являются динамическими элементами, отображающими активированный компонент. Технически компоненты будут отображаться как родственники директиве выхода маршрутизатора, а не внутри нее. Выходы маршрутизатора также могут быть вложены друг в друга, образуя отношения между родительским и дочерним маршрутами.

Всякий раз, когда в приложении происходит навигация, маршрутизатор берет URL-адрес, по которому выполняется переход, и пытается сопоставить его с путем в дереве состояний маршрутизатора. Например, с той же конфигурацией, что и раньше:

URL localhost:4200/notes/15 направит и загрузит NoteComponent. Внутри NoteComponent сможет получить доступ к параметру 15 и отобразить соответствующее примечание. Значение пути, начинающееся с двоеточия, например :id,, называется обязательным параметром и будет соответствовать практически чему угодно (в данном случае соответствует 15). Путь, подобный localhost:4200/iamerror, не сможет соответствовать ни одному пути и вызовет ошибку.

В любой момент времени URL-адрес представляет собой сериализованную версию текущего активированного состояния маршрутизатора приложения. Изменения в состоянии маршрутизатора изменят URL-адрес, а изменения в URL-адресе изменят состояние маршрутизатора. Оба они представляют собой одно и то же.

Мы увидим внутреннее устройство алгоритмов, которые маршрутизатор использует для сопоставления пути с маршрутом, в следующей статье этой серии. На данный момент достаточно знать, что он использует стратегию выигрывает первый матч. Внутренне это реализовано как поиск в глубину, когда маршрутизатор соответствует первому пути, который использует весь URL-адрес.

Понимание того, как Angular может моделировать все возможности маршрутизации в приложении в виде дерева состояний маршрутизатора, является первым столпом маршрутизации. Второй столп, навигация, описывает, как переходить из одного состояния маршрутизатора в другое.

Жизненный цикл маршрутизатора

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

Во время этого цикла навигации маршрутизатор генерирует серию событий. Служба маршрутизатора предоставляет наблюдаемый объект для прослушивания событий маршрутизатора, который можно использовать для определения логики, такой как запуск анимации загрузки, а также для помощи в отладке маршрутизации. Некоторые примечательные события во время этого цикла:

NavigationStart: Представляет начало цикла навигации. NavigationCancel: Например, охранник отказывается идти по маршруту. RoutesRecognized: Когда URL-адрес был сопоставлен с маршрутом. NavigationEnd:Запускается при успешном завершении навигации.

Полный список событий, расширяющих класс RouterEvent, можно найти здесь.

Учитывая приведенную выше конфигурацию, давайте рассмотрим, что происходит при указании URL-адреса http://localhost:4200/notes/42. Обзор выглядит следующим образом.

  1. Во-первых, любые перенаправления должны быть обработаны, поскольку нет смысла пытаться сопоставить URL-адрес с состоянием маршрутизатора, пока у нас не будет окончательной версии этого URL-адреса. Поскольку в этом случае перенаправления отсутствуют, URL-адрес остается неизменным.
  2. Затем маршрутизатор использует стратегию первое совпадение-выигрыш-с-отслеживанием для сопоставления URL-адреса с состоянием маршрутизатора, определенным в конфигурации. В этом случае он будет соответствовать path: ‘notes', а затем path:’:id'. NoteComponent связан с этим маршрутом.
  3. Поскольку было найдено соответствующее состояние маршрутизатора, маршрутизатор затем проверяет, есть ли какие-либо средства защиты, связанные с этим состоянием маршрутизатора, которые могут препятствовать навигации. Например, возможно, только пользователи, которые вошли в систему, могут просматривать заметки. В этом примере нет охранников. Мы также не используем преобразователи для предварительной выборки данных для этого маршрута, поэтому маршрутизатор продолжает навигацию.
  4. Затем маршрутизатор активирует компонент, связанный с этим состоянием маршрута.
  5. Маршрутизатор завершает навигацию. Затем он ожидает другого изменения состояния / URL-адреса маршрутизатора и повторяет процесс снова.

Эти события можно просмотреть в консоли браузера, передав параметр enableTrace: true методу forRoot маршрутизатора.

В качестве альтернативы компонент может получить доступ к потоку событий маршрутизатора, внедрив службу Router и подписавшись на ее events наблюдаемый:

Вы можете увидеть несколько примеров событий маршрутизатора, перемещаясь между представлениями в этом Stackblitz и проверяя консоль:



В статье о навигации этой серии подробно рассматривается этот цикл и его события.

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

Модули функции отложенной загрузки

Третий столп Angular routing - это концепция ленивой загрузки модулей. По мере роста приложения с течением времени все больше и больше его функциональных возможностей будет заключаться в отдельные функциональные модули. Например, веб-сайт, на котором продаются книги, может иметь такие модули, как книги, пользователи и т. Д. Скорее всего, не все эти данные будут отображаться при первой загрузке приложения, поэтому нет причин включать их все в основной пакет. Это приведет только к раздутию этого файла и увеличению времени загрузки при загрузке приложения. Лучше загружать эти модули по запросу всякий раз, когда пользователь переходит к ним, и маршрутизатор Angular достигает этого посредством отложенной загрузки.

Пример отложенной загрузки модуля выглядит так:

Обратите внимание, что значение, переданное в loadChildren, является строкой, а не ссылкой на компонент. Следует проявлять осторожность, чтобы избежать любых ссылок на какую-либо часть отложенно загружаемого модуля (например, на импорт модуля). В противном случае возникнет зависимость от этого модуля во время компиляции, и Angular должен будет включить его в основной пакет, тем самым побеждая цель ленивой загрузки.

Маршрутизатор начнет получать любые лениво загруженные модули во время фазы применения перенаправлений / сопоставления URL-адресов цикла навигации:

Как указано в config.ts:

Маршрутизатор будет использовать зарегистрированный NgModuleFactoryLoader для получения NgModule, связанного со строкой loadChildren. Затем он извлечет набор маршрутов, определенных в этом модуле NgModule, и прозрачно добавит эти маршруты в основную конфигурацию.

Таким образом, маршруты, определенные в конфигурации отложенного модуля, будут загружены в основную конфигурацию, и затем их можно будет сопоставить и направить.

Позже в этой серии мы еще поговорим о отложенной загрузке.

В оставшейся части этой серии мы подробно рассмотрим детали реализации каждого из трех вышеперечисленных столпов, начиная с состояний маршрутизатора и сопоставления путей. Пожалуйста, не переключайтесь!

Прочтите остальную часть серии здесь:

Состояния маршрутизатора и соответствие URL

Цикл навигации маршрутизатора

Ленивая загрузка и предварительная загрузка