AngularInDepth уходит от Medium. Эта статья, ее обновления и более свежие статьи размещены на новой платформе inDepth.dev

Одна из самых больших проблем любого приложения - это управление данными. В мире Angular существует множество шаблонов для управления вашим приложением. Обычно они включают использование декораторов, таких как Input и Output, или таких вещей, как RxJs Observables для отслеживания изменений данных. Однако существует технология реактивного состояния, которая решает эту проблему, под названием NgRx.

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

В некоторых из своих недавних постов я упоминал созданное мной приложение под названием Goose Weather. Вы можете просмотреть его на https://www.gooseweather.com. Создание этого было отличным опытом обучения, потому что я получил возможность использовать множество новых функций Angular и NgRx. Приложение использует API NOAA и службу OpenWeatherMapAPI для создания прогноза погоды. Я знаю, что использовать NgRx для этого приложения немного сложно, но распространение данных о погоде на видеокарты показывает, как вы могли бы использовать его в более сложном приложении.

Если вам интересно, я назвал Goose Weather в честь своей жены. В детстве она хотела стать метеорологом, и мое прозвище для нее всегда было Гусь (звучит очередь). Поэтому, когда я придумывал название, «Goose Weather» была естественным победителем. Отвечая на ваш следующий вопрос, она так и не стала метеорологом, а вместо этого стала налоговым бухгалтером (храп в очереди).

В следующих разделах я собираюсь обсудить, как работает NgRx, а затем расскажу, как я его настраивал с помощью Goose Weather.

Как работает NgRx

NgRx состоит из пяти частей:

  1. Магазин
  2. Редукторы (и метаредукторы)
  3. Действия
  4. Селекторы
  5. Эффекты

Базовая реализация выглядит следующим образом:

  1. Состояние вашего приложения сохраняется в торе. store неизменен.
  2. Компоненты вашего приложения могут подписаться на магазин и получать автоматические обновления состояния с помощью селекторов.
  3. Селекторы позволяют компонентам получать фрагмент (часть) состояния вашего приложения, а также изменять состояние с помощью селектора. функции.
  4. Действия изменяют состояние хранилища с помощью редукторов (функций), которые позволяют вносить изменения, сохраняя при этом неизменность.
  5. Мета-редукторы (не показаны) - это перехватчики, с помощью которых вы можете выполнить предварительную или постобработку действий до того, как они будут вызваны.
  6. Эффекты возникают в результате действий, а также могут создавать действия при вызове. Основная ответственность Эффекты заключается в создании побочных асинхронных эффектов (например, вызовов служб к API), которые в конечном итоге генерируют другие действия.

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

NgRx также может упростить архитектуру вашего приложения, так как вы можете использовать его для замены привязок свойств и событий. Очевидно, что это основано на проекте, и есть много способов использовать гибридный подход или вообще не использовать NgRx. В качестве рекомендаций по структурированию вашего приложения я рекомендую курс Pluralsight Angular NgRx: начало работы с Дунканом Хантером и Деборой Курата.

Перед тем, как вы начнете кодировать

Прежде чем мы начнем, я должен отметить, что с NgRx нужно немного научиться. Я рекомендую, в частности, ознакомиться с операторами RxJS и наблюдаемыми. Также полезно взглянуть на документацию RxJS на сайте Reactivex Overview и Документацию Angular по RxJS. Я также предполагаю, поскольку вы читаете сообщение в блоге о NgRx, что вы уже знакомы с основами проектов Angular2 + и интерфейса командной строки. Если нет, рекомендую ознакомиться с документацией здесь.

Я действительно пришел к пониманию наблюдаемых через обработку ошибок. Я написал пост под названием Обработка исключений с помощью эффектов NgRx, в котором есть хорошее введение в использование наблюдаемых и обещаний и т. Д., Если у вас также есть проблемы с пониманием RxJ.

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

NgRx с Goose Weather

Общее приложение Goose Weather - это, по сути, основной компонент погоды, отображающий несколько дочерних компонентов, которые являются картами материалов. Кроме того, на главной панели инструментов есть поле ввода, которое позволяет выбрать другое место. Доступные для выбора местоположения предварительно настроены с использованием столиц всех 50 штатов США (не международных, поскольку используются API NOAA).

Наша цель:

Отправляйте погоду в магазин при первом запуске приложения.

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

Подключите все видеокарты для автоматического обновления при обновлении значений магазина.

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

Приступим к кодированию!

  • Перейдите в репозиторий GitHub и используйте следующую команду, чтобы открыть ветку learn-ngrx:
git clone - single-branch - branch learn-ngrx https://github.com/andrewevans02/goose-weather.git
  • Затем перейдите к клонированному репо с cd goose-weather.
  • Теперь установите зависимости запуском npm i.
  • Goose Weather также использует Open Weather Map API, чтобы получить последний прогноз. Чтобы использовать этот API, вам необходимо создать бесплатную учетную запись и ключ, используя руководство по началу работы на их веб-сайте.
  • Получив ключ API Open Weather Map, экспортируйте его как переменную среды с именем $OPEN_WEATHER_MAP_API_KEY с
export OPEN_WEATHER_MAP_API_KEY='<your_open_weather_map_api_key>'
  • После экспорта ключа и переменной среды запустите npm run environment-variables, чтобы вставить ключ API в файлы environment.ts и environment.prod.ts проекта. Чтобы сохранить переменные среды, добавьте экспорт в свой профиль bash или переменные среды.
  • Теперь давайте запустим проект с ng serve в корневом каталоге проекта, чтобы убедиться, что он запущен.
  • Откройте браузер на http://localhost:4200/, и вы увидите страницу Goose Weather.

В следующих разделах я расскажу о настройке NgRx. Чтобы упростить (и ускорить), я также собираюсь немного схитрить и предоставить вам GitHub Gists в следующих разделах. Просто скопируйте и вставьте их, и я буду давать объяснения по мере прохождения каждого шага.

Установите зависимости и создайте проект

Сначала мы установим зависимости NgRx и воспользуемся схемой NgRx для начальной загрузки приложения. Угловые схемы действительно полезны, и вы можете использовать их для быстрого создания шаблонов сайта. Для получения дополнительной информации об Angular Schematics, я рекомендую проверить этот пост в Angular Blog здесь.

  • Сначала выполните следующую команду, чтобы установить зависимости. Он добавит в проект библиотеки для store, эффектов, store-devtools и схем.
npm i @ngrx/store @ngrx/effects @ngrx/store-devtools @ngrx/schematics --save
  • Выполните следующую команду в корне проекта, чтобы изменить интерфейс командной строки для использования схемы NgRx.
ng config cli.defaultCollection @ngrx/schematics
  • Запустите схемы, чтобы сгенерировать действия и редукторы, которые составят хранилище:
ng generate store AppState --root --module app.module.ts
  • Чтобы завершить первоначальную настройку, выполните следующие две команды, чтобы сгенерировать действия погода и местоположение, которые мы будем создавать.
ng generate action actions/weather
ng generate action actions/location
  • Когда вы закончите этот шаг, у вас должны появиться папки actions и reducers, как показано здесь:

  • Если вы откроете файл app.module.ts, вы также должны увидеть импорт для модуля store и импорт, говорящий приложению не работать с store-devtools, если оно находится в рабочей среде.

Определение состояния и редукторов

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

Если вы вернетесь к вступлению, вы вспомните, как потоки работают с NgRx: действия проходят через редукторы для изменения состояния приложения .

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

Теперь в своем проекте откройте файл reducers/index.ts и скопируйте и вставьте следующее:

Что теперь делает этот файл? Ну, очень много, но это не так страшно, как кажется. Давайте проработаем его построчно.

Здесь мы выясняем, как выглядит состояние приложения. Есть много способов сделать это, и я просто использую здесь наивный подход, поскольку взаимодействие этого приложения с NgRx очень простое.

Структуры здесь определяют состояние приложения, которое включает:

  1. Местоположение
  2. Погода

Здесь мы определяем два редуктора (1) погода и (2) местоположение. Если вы заметили, они возвращают объекты полезной нагрузки. Это обычное соглашение с NgRx. Полезная нагрузка - это то, чем будет обновляться хранилище. Если полезная нагрузка состоит только из одного значения, часто принято не включать payload оболочку и просто возвращать значение (то есть action.locationData вместо action.payload.locationData). Я просто добавляю payload сюда, чтобы показать возможные условные обозначения, которые можно использовать.

Здесь мы определяем селекторы. Селекторы - это способы прямого доступа компонентов вашего приложения к состоянию. Номенклатура для этого должна относиться либо к состоянию в целом, либо, если вы выбираете определенные части, вы выбираете срез состояния. Селекторы также могут преобразовывать состояние и иметь множество других потенциальных функций, которые можно использовать для взаимодействия с магазином. Это приложение довольно простое, поэтому нам просто нужен селектор для данных о погоде (selectWeather) и для ошибок (selectError). Более сложные приложения включают в себя несколько селекторов и даже функции внутри селекторов. Я рекомендую ознакомиться с документацией, если вам нужна дополнительная информация о том, как работают селекторы, здесь.

Наконец, в конце файла мы определяем метаредукторы. Это хуки, которые позволяют предварительно обрабатывать действия или добавлять промежуточное ПО. Вы можете определить их для поиска таких действий, как INIT или UPDATE, которые являются действиями по умолчанию, которые NgRx выполняет при запуске приложения или при изменении хранилища соответственно. Мета-редукторы - также отличный способ справиться с localStorage. Я не определил ни одного для этого приложения, потому что оно очень простое, но оно может быть очень мощным, и я рекомендую проверить сообщение Алекса Окрушко о способах улучшения использования NgRx здесь.

Определение действий

Следующим шагом является определение действий, которые будут отправлять изменения в хранилище, используя состояние приложения и редукторы, которые мы только что определил. Действия следует рассматривать как события и находиться рядом с тем местом, откуда они были отправлены. Также рекомендуется включить имя страницы, с которой запускается действие, при объявлении типа действия. Так, например, «Местоположение загрузки [Домашняя страница]» будет действием типа LoadLocation, запускаемым с домашней страницы.

Откройте файл actions/location.actions.ts и скопируйте и вставьте в него следующее:

Что делает этот файл? Это определение того, как будут выглядеть действия с вашим местоположением. В первом разделе мы определяем типы действий:

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

Наконец, вы видите, что эти классы действий экспортируются в проект:

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

Чтобы завершить этот раздел, откройте файл actions/weather.actions.ts и скопируйте и вставьте следующее. Я не собираюсь здесь подробно останавливаться на коде погодных действий, поскольку он следует соглашению, аналогичному тому, что я только что объяснил для действий с определением местоположения.

Кстати, действие LoadWeather здесь считается действием «выборки» в NgRx. Действия выборки обычно следуют соглашению иметь (1) Load, (2) LoadSuccess или (3) LoadFailed. Это необходимо для обработки самой нагрузки, успеха и неудачи соответственно. У Goose Weather есть только одна загрузка данных о погоде, поэтому я оставил ее здесь как «LoadWeather». В более сложном приложении это можно было бы переименовать.

Создание эффекта для действий

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

Вспоминая то, о чем мы говорили во вступлении:

Эффекты возникают в результате действий, а также могут создавать действия при вызове. Основная ответственность Эффекты заключается в создании побочных асинхронных эффектов (например, вызовов служб к API), которые в конечном итоге генерируют другие действия.

Для приложения Goose Weather давайте рассмотрим следующие сценарии:

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

Во-первых, давайте воспользуемся схемой, чтобы создать эффект. Перейдите в корень проекта и запустите следующее:

ng generate effect effects/weather - root -m app.module.ts

Это создаст эффект под названием WeatherEffects в папке /effects. Он также добавит импорт эффектов в модуль приложения, как вы видите здесь:

Теперь, когда вы создали свой эффект, скопируйте и вставьте следующее в weather.effect.ts файл:

Итак, что это делает и как это работает?

  • С помощью декоратора Effect экземпляр приложения NgRx узнает об этом эффекте при запуске.
  • Этот эффект прослушивает действия типа LoadLocations, а затем использует mergeMap для передачи данных о местоположении от действия до вызова в службу погоды приложения.
  • Затем эффект отправит новое действие LoadWeather, чтобы включить новую информацию о прогнозе.
  • Когда отправка в магазин завершена, магазин обновляется
  • Если при вызове службы возникают какие-либо ошибки, эффект отправляет в хранилище действие LocationsError с ошибкой.

Для более подробного изучения того, как работает обработка ошибок, ознакомьтесь с моим сообщением Обработка исключений с помощью эффектов NgRx. Особая благодарность Тиму Дешрайверу и Алексу Окрушко за их помощь в этом.

Подключение NgRx к угловым компонентам

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

Сначала откройте src/weather/weather.component.ts файл и вставьте хранилище в конструктор хранилища с закрытым store: Store<AppState>.

Теперь измените методы savePosition и onSelectionChanged, чтобы они выглядели следующим образом:

Что это сделало? В обоих методах savePosition иonSelectionChanged изменение местоположения вызывает отправку LoadLocations для обновления магазина. Когда срабатывает действие LoadLocations, срабатывает созданный нами ранее WeatherEffect, чтобы получить прогноз для этого местоположения.

Также обратите внимание, что перед вызовом службы в onSelectionChanged отправляется действие LoadWeather со значением null для weatherData. Это было сделано для того, чтобы создать условие, при котором счетчики прогресса карты показывают, когда данные о погоде извлекаются. В качестве альтернативного подхода я также мог бы установить weatherData в null при отправке действия LoadLocations. Я оставил все как есть, чтобы четко определять местоположение и погодные условия.

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

Затем включите отображение сообщения об ошибке в верхней части файла weather.component.html с изменением шаблона следующим образом:

Теперь, когда мы обновили компонент погоды, нам также необходимо обновить компоненты, которые отображают прогноз погоды в виде карточек. Компоненты, отображающие прогноз погоды, находятся в каталоге «/ cards», как показано здесь:

Я просто покажу вам, как обновить компонент еженедельный прогноз. Вам также нужно будет проделать то же самое с остальными карточками.

Сначала скопируйте и вставьте это в файл weekly-прогноз.component.ts:

При этом создается наблюдаемый объект, который становится потоком данных из хранилища с помощью селектора selectWeather.

Затем скопируйте и вставьте следующее в файл weekly-прогноз.component.html здесь:

Как вы видите здесь, асинхронный конвейер обрабатывает наблюдаемую подписку и показывает только наличие данных (в противном случае отображается счетчик).

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

Очистка файлов

Последний шаг перед запуском приложения - просто переместить некоторые файлы так, чтобы они были более тесно связаны с тем, где они используются. Схемы, которые мы использовали для создания нашего приложения, создавали папки для эффектов, редукторов и действий. Кроме того, если вы заметили, я добавил папку services, которая включает основную службу, которая получает прогноз погоды. Теперь, когда мы все построили, давайте переместим файлы действий, файлы эффектов и служебные файлы в папку компонентов погоды. Это связано с тем, что погодный компонент - это то, что напрямую использует эти действия, эффекты и сервисы. Итак, теперь файлы NgRx связаны с компонентом, который их использует. Ваша последняя папка Погода должна выглядеть так:

Собираем все вместе

Теперь, когда вы встроили NgRx в приложение и подключили его к компонентам, перейдите в свой терминал и запустите ng serve, чтобы увидеть его в действии. Вы должны увидеть что-то вроде следующего:

Если вы откроете расширение Redux Devtools, вы также сможете увидеть, как обновляется состояние при запуске приложения:

Заключительные мысли

Я надеюсь, что мой пост дал вам хорошее представление об использовании NgRx. Мое погодное приложение использует только несколько действий и редукторов, но NgRx намного надежнее и может делать гораздо больше. Я рекомендую вам ознакомиться с официальной документацией сайта NgRx и поближе познакомиться с технологией. Не стесняйтесь оставлять комментарии и наслаждайтесь полетами с NgRx!

Особая благодарность Алексу Окрушко, Тиму Дешрайверу и Жан-Этьену Лавалле за помощь в написании этой статьи!