Маршрутизация 101 в Angular 9+

Чему мы научимся?

  • Показывать и скрывать компоненты, используя вместо свойств input/output маршруты.
  • Поддерживать взаимосвязь этих компонентов, передавая по маршруту данные.
  • Настраивать маршруты в модуле маршрутизации.
  • Использовать routerLink для настройки событий клика и передачи данных по маршруту.

С помощью свойств input/output можно управлять отображением UI для пользователя. Такая настройка определенно работает и отлично подходит для доставки дочерним компонентам необходимых данных, но по части чистоты такой код не особо пригляден. В нем input были разбросаны буквально повсюду, что делало его излишне повторяющимся.

К примеру, вот app.component.html того проекта:

<app-toolbar></app-toolbar>


<app-habit-form *ngIf="formOpen; else allHabits" 
  (onExit)="closeForm()"
  [habit]="editHabit"></app-habit-form>

<ng-template #allHabits>
  <app-all-habits 
    (addEvent)="onAdding()"
    (editEvent)="onEditing($event)"></app-all-habits>
</ng-template>

Здесь под управлением компонента приложения находится много логики, и он же берет на себя работу по организации траффика. При этом сложность представляет необходимость передачи данных из all-habits в компонент приложения просто для того, чтобы отправить их обратно в компонент habit-form. Компонент приложения вообще не должен заниматься этими данными.

Чем отличается маршрутизация в SPA от маршрутизации в HTML?

В SPA (одностраничное приложение) вы предоставляете весь контент и данные через одну страницу. Это приложение просто показывает разный HTML, CSS и JS-код на основе своего состояния. Данные можно отправлять в оба направления между выполняющимся приложением и сервером, не прибегая к перезагрузке страницы. Поэтому при выполнении простых запросов нет нужды ждать возвращения ресурсов. Вот как выглядит точка входа для всего кода приложения, расположенная в index.html::

<body>
    <app-root></app-root>
</body>

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

Когда маршрутизация уместна?

  • Когда нужно отображать другое содержимое. Это можно представить через призму классического варианта не-SPA страниц. Новый маршрут должен направлять вас к совершенно новому представлению приложения. Если целью добавляемого функционала будет скрывать остальную часть приложения и показывать новое самостоятельное представление, то стоит рассмотреть для отображения нового контента именно маршрутизацию. 
  • Когда нужно отобразить содержимое для элементов из списка. Параметры маршрута упрощают доступ к таким ответвлениям, как страницы подробностей или редактирования, путем передачи в маршрут ID или индекса.
  • Когда требуется управление доступом на основе ролей. Эта тема выходит за рамки данного руководства, но защита маршрутов Angular обычно используется, когда вам нужно убедиться, чтобы пользователи, соответствующие установленному критерию, не могли обращаться к контенту, отображаемому маршрутом. Новый маршрут может инкапсулировать страницу, содержащую чувствительную информацию, и перенаправлять с нее неавторизованных пользователей.

Настройка

Среда разработки

  • Если вы работали с приложением Angular на своей машине, то убедитесь, что на ней установлены Node.js и Angular CLI.
  • Я покажу, как получить стартовые файлы с помощью Git, для чего, если вы этого еще не делали, также потребуется его установить.

Стартовые файлы

Стартовый проект можно скачать со следующего репозитория. Чтобы сделать это из командной строки, перейдите в расположение, куда хотите загрузить приложение и введите эту команду Git:

git clone https://github.com/jessipearcy/basic-routing-start.git

Так вы скопируйте файлы в нужное расположение. Далее запустите следующие две команды, чтобы перейти в каталог и установить необходимые для запуска приложения Angular пакеты:

cd basic-routing-start
npm ci

По завершении установки пакетов введите в командной строке ng serve, нажмите enter и перейдите на http://localhost:4200, где увидите запущенное приложение.

Реализация маршрутизации

Сейчас наше приложение собирается, но пока не представляет особого интереса, так как мы еще не настроили ни одного маршрута.

Откройте app-routing.module.ts. Здесь вы увидите, что по умолчанию Angular устанавливает свойство routes как пустой массив:

const routes: Routes = [];

Давайте создадим маршрут, чтобы домашняя страница при загрузке приложения отображала список привычек (habits):

const routes: Routes = [
  { path: '', component: AllHabitsComponent }
];

Этот объект сообщает приложению, что при отрисовке базового URL (для текущего тестового приложения им будет http://localhost:4200/) нужно отображать AllHabitsComponent. Не забудьте добавить для этого компонента инструкцию импорта.

Далее нам нужно сообщить Angular, куда помещать содержимое, которое он будет передавать из маршрута. Добавьте в app.component.html после панели инструментов следующий элемент:

<router-outlet></router-outlet>

Супер! Теперь у нас есть содержимое, с которым можно работать.

Переход к форме

Чтобы сделать обвязку страницы и получить возможность добавлять новые привычки, нам нужно познакомиться с Angular-свойством routerLink. При добавлении этого свойства в элемент он начинает вести себя почти как тег <a> и будет выполнять перенаправление в указанное расположение.

Для начала добавим новый маршрут для срабатывания при нажатии кнопки. Перейдите в app-routing.module.ts и добавьте в массив routes еще один объект пути:

const routes: Routes = [
    { path: 'habit-form', component: HabitFormComponent },  
    { path: '', component: AllHabitsComponent },
];

Далее в Add New Habit добавим routerLink, направляющий на новый маршрут:

<h1>All Habits</h1>
  <button mat-raised-button color="accent" routerLink="/habit-form">
    Add New Habit
  </button>

Сохраните все это и попробуйте кликнуть по кнопке  —  если все сделано правильно, то у вас появится возможность переходить в форму. Если проверить этот URL в браузере, то можно увидеть маршрут, по которому произошел переход:

http://localhost:4200/habit-form

Переход из компонента

Часто с переходом из одного представления в другое связана некоторая логика. В таких случаях вместо использования routerLink можно переходить в компонент из функции. Давайте познакомимся со встроенным в Angular классом Router:

Обновите habit-form.component.ts так:

  • Импортируйте Router из @angular/router.
  • Внедрите свойство маршрутизатора в конструктор класса.
  • Используйте функцию navigate() маршрутизатора для перенаправления приложения. 
//...Код опущен...
import { Router } from '@angular/router';

export class HabitFormComponent implements OnInit {
  //...Код опущен...

  constructor(private router: Router) {}

  //...Код опущен...

  public onSubmit() {
    const habit = this.habitForm.value as Habit;

    if (this.editing) {
      this.habits.splice(this.editingIndex, 1, habit);
    } else {
      this.habits.push(habit);
    }
    this.exitForm();
    this.router.navigate(['/']);
  }

//...Код опущен...
}

Теперь событие клика, связанное с формой привычки, будет как создавать новую привычку в массиве привычек, так и перенаправлять пользователя обратно в общий список. Обратите внимание, что новая привычка была добавлена динамически и отображается сразу после инициализации компонента all-habits.

Установка параметра маршрута

Чтобы отредактировать элемент из списка можно передавать его индекс в маршрут и затем выбирать этот индекс в форме, узнавая таким образом о выполняемом редактировании. Для этого потребуется внести новый маршрут. Добавьте в app-routing.module.ts следующий маршрут, включающий параметр id:

const routes: Routes = [
    { path: 'habit-form/:id', component: HabitFormComponent },
    { path: 'habit-form', component: HabitFormComponent },
    { path: '', component: AllHabitsComponent },
];

Внимание: порядок очень важен! Более конкретные маршруты нужно размещать над менее конкретными. Если маршрут с параметром перечислить вторым, то приложение будет сначала перенаправлять в форму привычки и никогда не выберет id. 

Затем мы добавим в all-habits.component.html еще один атрибут routerLink и на этот раз передадим в маршрут значение индекса habit, по которой кликнули:

<mat-icon 
    class="habit-icon" 
    color="primary" 
    [routerLink]="['/habit-form', i]">edit
</mat-icon>

Несколько пояснений:

  • В этом примере использованы квадратные скобки, чтобы передаваемый текст в двойных кавычках оценивался как выражение JavaScript. Квадратные скобки сделают это для любой привязки Angular. Если же их не использовать, то содержимое между двойными кавычками будет расценено как строковый литерал.
  • Передаваемая нами переменная i будет отвечать за заполнение параметра :id маршрута. Она соответствует индексу habit, которую мы собираемся редактировать, и позволяет нам найти ее в форме.

В данный момент, если вы кликните по иконке редактирования, то увидите, что в маршрут попадает индекс соответствующей привычки. Но мы еще не заполняем форму отредактированной привычкой  —  давайте это исправим.

Обращение к параметру маршрута

Через habit-form.component.ts мы будем обращаться к отправляемому в маршрут id при помощи Angular-класса ActivatedRoute. Внесите следующие изменения:

//...Код опущен...
import { Router } from '@angular/router';

export class HabitFormComponent implements OnInit {
  //...Код опущен...

  constructor(private router: Router) {}

  //...Код опущен...

  public onSubmit() {
    const habit = this.habitForm.value as Habit;

    if (this.editing) {
      this.habits.splice(this.editingIndex, 1, habit);
    } else {
      this.habits.push(habit);
    }
    this.exitForm();
    this.router.navigate(['/']);
  }

//...Код опущен...
}

Обратите внимание на следующие изменения:

  • Мы используем свойство Route из ActivatedRoute для получения параметра из URL. Строка, которую мы передаем в get(), соответствует имени определенного нами в app-routing.module.ts параметра.
  • Метод get() возвращает строку, поэтому мы используем в строке 21 оператор + для приведения этой строки к числу, которое сможем сохранить и использовать в editingIndex.
  • Наличие параметра id помогает нам понять, находимся мы в режиме редактирования или нет.
  • Не забудьте при выходе из формы установить editing снова на false, иначе приложение может повести себя неожиданным образом.

И наконец, нам нужно убедиться, что кнопка Cancel выполняет перенаправление, а не пробует отправить пустую habit. Обновите в habit-form.component.html кнопку Cancel, добавив пустой атрибут routerLink:

<button mat-raised-button routerLink="">Cancel</button>

Теперь она будет перенаправлять обратно к общему списку.

Заключение

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

  • Настраивать базовые маршруты в app-routing.module.ts.
  • Пердавать данные в маршрут через атрибут routerLink в HTML.
  • Переходить по другому маршруту из функции в контроллере при помощи Router и navigate().
  • Обращаться к параметру маршрута, используя ActivatedRoute и paramMap.

Благодарю за внимание  —  надеюсь вы узнали что-то новое и полезное!

Ресурсы

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Jessi Pearcy: Routing 101 in Angular 9+