Это вторая часть серии статей. Часть 1 - это введение в @ ngrx / component-store.

В предыдущей части этой серии мы рассмотрели, как настроить хранилище компонентов. Мы также обсудили, когда следует рассмотреть возможность использования хранилища компонентов, и некоторые преимущества использования хранилища компонентов. Теперь давайте углубимся и повеселимся с хранилищем компонентов, которое мы создали в предыдущей части. Весь код из этой части можно найти здесь, на GitHub.

Синхронизация компонентов

Один из первых и наиболее распространенных случаев использования @ ngrx / component-store - синхронизация сложных взаимодействий между компонентами. В этом примере мы будем отображать два редактора, когда пользователь нажимает кнопку редактирования в таблице. Чтобы добавить еще один компонент в микс, мы добавим компонент, который будет отображать имя редактируемого человека. Все компоненты будут синхронизированы независимо от того, какой компонент редактируется.

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

Первые изменения, которые мы вносим, ​​касаются PersonState. Добавляя editorId, мы можем создавать селекторы, которые могут захватывать текущего человека, если это необходимо. Это также позволит нам сравнить editorId с id человека в строке, чтобы потенциально изменить кнопки или стили CSS в зависимости от редактируемого элемента. Затем мы добавляем editedPerson. Это важно, потому что мы хотим, чтобы хранилище было неизменным. Мы могли бы просто добавить селектор, который извлекал бы редактируемый Person из магазина и затем изменял его, чтобы все синхронизировалось. К сожалению, это изменит наше состояние. Подобные побочные эффекты делают наш пользовательский интерфейс непредсказуемым и сводят на нет многие преимущества использования @ ngrx / component-store.

Следующие модификации - это несколько простых селекторов для editorId и editedPerson. Обратите внимание на код отладки с использованием tap() и console.log(). Поскольку селекторы - это просто наблюдаемые объекты RxJS, мы используем pipe() и любой желаемый оператор RxJs. Это часто бывает полезно для отладки и отслеживания взаимодействий в нашем хранилище компонентов. В реальном проекте я предлагаю вам изучить возможность создания оператора RxJS с возобновляемым журналированием. Существует множество примеров, и вы можете адаптировать оператора под свои нужды.

Затем мы добавляем несколько простых средств обновления, позволяющих изменять новые свойства состояния. Помните в ваших программах обновления, чтобы избежать изменения состояния. В этом примере мы делаем это с помощью оператора распространения объекта. Если включены строгие проверки на null, TypeScript предотвратит передачу null нашим программам обновления. Вот почему мы указываем, например, number | undefined.

Последнее изменение - добавить эффект к нашему PersonStore. Эффекты в @ ngrx / component-store очень похожи на Эффекты NgRx. Главное отличие в том, что в NgRx вы реагируете на Действия. В @ ngrx / component-store действий нет. Вызов метода editPerson() на PersonStore с id для редактирования приведет к тому, что наш эффект выдаст значение, которое будет обрабатываться pipe() на personId$ наблюдаемом, созданном вызовом this.effect(). Это немного менее гибко, чем Действия и Эффекты NgRx, но все же позволяет нам предоставлять пользователям согласованный API. Согласованный API позволяет потребителю нашего PersonStore знать, что нужно вызывать editPerson() с id. Наш эффект может обрабатывать вызов setEditorId(id), получение personToEdit и вызов setEditedPerson(personToEdit).

Об этом эффекте следует отметить несколько интересных моментов. В строке 4 мы вызываем withLatestFrom(this.people$). Поскольку параметром эффекта является наблюдаемая RxJS, мы можем использовать операторы RxJS для получения данных из других селекторов для использования в наших эффектах. Помните, что withLatestFrom() будет ждать испускания наблюдаемого, прежде чем испускать себя. Если наблюдаемый объект не сработал, возможно, вам потребуется вызвать startWith() для наблюдаемого объекта, переданного в withLatestFrom().

В строке 13 мы используем оператор распространения для создания клона personToEdit. Это поможет нам избежать случайного изменения фактического Person из массива people.

Эффекты бывает сложно получить правильно. Хорошее понимание наблюдаемых RxJS и оператора pipe() поможет в отладке. Щедрая помощь в виде tap() или специального оператора регистрации будет очень ценна для отслеживания того, что и когда испускается.

Теперь, когда PersonStore готов, нам нужно подключить все к пользовательскому интерфейсу. Чтобы перейти в режим редактирования, мы добавили кнопку в PersonListComponent, которая имеет следующий обработчик кликов (click)=”editPerson(element.id)”. Затем мы добавляем метод editPerson() к PersonListComponent.

Этот метод действительно прост. Мы просто вызываем PersonStore.editPerson() с переданным обработчику id. В этом сила использования эффектов. Мы предоставляем очень простой интерфейс, чтобы потребители могли делать мощные вещи с нашим магазином.

Чтобы поместить эти данные в PersonContainerComponent, мы добавляем наблюдаемую для селектора editedPerson$.

editedPerson$ = this._personStore.editedPerson$;

Затем нам нужно получить шаблон, чтобы показать наши новые компоненты, которые мы хотим синхронизировать.

Мы используем оператор async, чтобы получить последнее переданное значение из наблюдаемого editedPerson$. В этом случае компоненты представляют собой простые компоненты Angular. Однако необходимо внести небольшое изменение в компонент <component-store-edit-person>, чтобы обновить хранилище, чтобы все наши компоненты обновлялись при редактировании Person.

Вы заметите, что на <input> выше мы добавили событие ngModelChange, которое вызывает personEdited(). Это позволяет нам реагировать, когда Angular обнаруживает, что <input> изменился. В edit-person.component.ts мы просто вызываем PersonStore.setEditedPerson() с текущим значением свойства person. Из-за однонаправленного потока @ ngrx / component-store данные обновляются во всех компонентах. Вам не нужно беспокоиться о циклах обновлений или других странностях, вызванных одновременным редактированием в двух местах.

Сохранение или отмена обновлений

На данный момент у нас есть забавная игрушка, но в ней нет никаких полезных функций. Ни одно из наших обновлений нельзя сохранить или отменить. Кроме того, после редактирования компонента вы не можете прекратить редактирование. Редактор всегда открыт. Давай исправим это. Мы создадим SavePersonComponent и добавим его к DisplayPersonComponent и EditPersonComponent. Это позволит нам сохранить или отменить изменения текущего Person в любом из трех компонентов.

Для этого SavePersonComponent представляет собой простой компонент Angular с двумя кнопками. У каждого из них есть обработчик кликов. Важная часть - это файл .ts.

В нашем SavePersonComponent мы вводим PersonStore, а затем вызываем cancelEditPerson() или saveEditPerson() для соответствующего действия. Сохраняя API для нашего PersonStore простым и последовательным, мы можем упростить людям взаимодействие с PersonStore. Вам не нужно вводить здесь PersonStore. Вы можете сохранить эти компоненты как чистые компоненты презентации и взаимодействовать только с PersonStore в вашем компоненте контейнера. Мы вводим их в дочерние компоненты в демонстрационных целях. При использовании @ ngrx / component-store в производстве ваша команда должна выбрать шаблон и придерживаться его. Последовательность является ключевым моментом при работе с любой технологией NgRx в команде.

Теперь, когда мы понимаем, насколько просто добавить в наши компоненты функции сохранения и отмены, давайте посмотрим, какие изменения необходимо внести в PersonStore. Сначала мы исследуем cancelEditPerson(), потому что это более простое изменение.

Есть много разных подходов к отмене правок. Мы могли бы создать новый эффект или пойти еще более простым путем. Я решил продемонстрировать, что PersonStore - это просто инъекционная служба Angular. Это означает, что мы можем просто добавить к нему простые методы и вызвать методы средства обновления. Мы можем извлечь вызовы средства обновления в clearEditedPerson(), потому что та же самая логика будет использоваться при успешном сохранении.

Метод cancel демонстрирует важность editedPerson. Имея клон Person, редактируемого в PersonStore, мы можем обновить этот Person, чтобы наши редакторы синхронизировались, но легко отменять изменения и не влиять на фактическую таблицу.

Метод сохранения немного сложнее. Вот изменения кода для функции сохранения:

Как видите, в функции сохранения внесено довольно много изменений. Опять же, мы могли бы использовать здесь какой-либо эффект, но я хотел показать другой паттерн, который часто бывает полезен. Бывают случаи, когда эффект - не лучшее решение. В таких случаях мы можем добавить наблюдаемое к объекту магазина. Вы можете увидеть это в PersonStore в строке 4. Этот шаблон можно использовать для задания «псевдо-действий». Хороший пример того, как я часто это использую, - это добавление уведомления о сохранении в хранилище компонентов. Я действительно не хочу добавлять свойство к состоянию, которое я должен установить, а затем сбросить, чтобы уведомить компоненты об успешном сохранении, потому что оно может быть подвержено ошибкам и нестабильно.

Метод saveEditPerson() очень прост. Он просто излучает saveEditPerson$ наблюдаемый. Настоящая «магия» - в конструкторе. В строке 12 мы создаем наблюдаемый конвейер, который вводит другие важные данные, а затем вызывает switchMap() метод savePerson() из StarWarsAPIService. В нашем примере это просто испускает значение, переданное после delay(). В строке 17 мы subscribe() к созданному наблюдаемому. Это важно, потому что, если мы не используем эффект, с нашим наблюдаемым ничего не произойдет, если нет подписчика.

В строке 19 у нас есть обработчик успеха для подписки. Когда сохранение выполнено успешно, мы вызываем updatePerson(), который является новым эффектом для обработки обновления массива. Затем мы вызываем clearEditedPerson(), чтобы вернуться в состояние, в котором мы не редактируем.

В строке 33 у нас есть новый эффект под названием updatePerson. Этот эффект объединяет селектор people$ с наблюдаемым из обновленных объектов Person. Затем он находит текущего человека в массиве people и заменяет его новым отредактированным человеком, если он найден. Обратите внимание на строку 43, что мы используем оператор распространения для клонирования массива перед внесением изменений. Это поможет предотвратить нежелательные мутации магазина. В реальном производственном приложении, где у вас будет более 9 элементов, я рекомендую использовать более надежный метод обновления массивов.

Заключение

На этом этапе наша демонстрация весьма полезна. Вы можете редактировать элементы в таблице и сохранять или отменять эти изменения. Однако, если пользователь начинает редактировать один элемент, а затем нажимает кнопку редактирования для другой строки, его изменения отменяются, и редактор с радостью открывает новую строку. Пользователь также может сохранять элементы, которые вообще не редактировались. Это нехороший пользовательский опыт, и их легко решить с помощью инструментов, которые мы обсуждали в этой статье. Если вы изучаете @ ngrx / component-store, я рекомендую попытаться создать решение самостоятельно. Вы можете найти код из этой статьи на GitHub.

Я надеюсь, что у вас есть представление о полезности @ ngrx / component-store и несколько идей о том, как вы можете использовать хранилище компонентов в своих приложениях. Не стесняйтесь писать мне любые вопросы или комментарии. Прежде всего, получайте удовольствие от изучения этого замечательного нового способа обеспечения согласованного состояния пользовательского интерфейса.

Третью часть этой серии статей можно найти здесь.

Готовы создавать максимально надежные веб-приложения? Присоединяйтесь к нам на саммите Reliable Web Summit 26-27 августа 2021 года, чтобы узнать, как это сделать! Конференция на высшем уровне по созданию надежных веб-приложений, включая следующие три основных направления:

  • Масштабируемая инфраструктура и архитектура
  • Поддерживаемый код
  • Автоматизированное тестирование

Https://reliablewebsummit.com/