AngularInDepth уходит от Medium. Более свежие статьи размещаются на новой платформе inDepth.dev. Спасибо за то, что участвуете в глубоком движении!

⚠️ ВАЖНО - ОБНОВЛЕНИЕ 29. 9. 2018

ngx-model официально устарел, используйте новую библиотеку @angular-extension/model, она имеет те же функции, но включает современный синтаксис и инструменты!

  • ✅ с использованием нового синтаксиса с возможностью встряхивания дерева providedIn: 'root'
  • ✅ больше нет импорта в @NgModule
  • ✅ схемы включены в один пакет
  • ✅ поддерживает ng add в проектах Angular CLI

Начиная

  1. установить с ng add @angular-extensions/model
  2. создавать услуги ng g @angular-extensions/model:model path/my-model
  3. проверить документацию

Вначале я думал о создании сообщения в блоге с небольшой демонстрацией на github, но со временем демоверсия превратилась во что-то похожее на полноценный сайт документации… (и, конечно, есть github репо »также доступно) и новое репо на github для ngx-модели

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

Это можно рассматривать как преемника оригинального поста 2015 года - Шаблон модели для Angular.js, обновленного для ландшафта 2017 года с помощью Angular 5, Typescript и RxJS.

Мотивация

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

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

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

Redux (и многие подобные redux-подобные библиотеки) работают с концепцией действий, которая требует выделения ВСЕХ типов действий для учета возможных мутаций сложного объекта состояния приложения. Эта ситуация может довольно быстро выйти из-под контроля…

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

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

Действия, диспетчеры и прочее - отнюдь не новые понятия. Я имел честь познакомиться с одной из сред Java CQRS в 2014 году, и позвольте мне просто сказать, что модуль статистики приложения никогда не работал должным образом, но нам потребовалось около года, чтобы заметить это.

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

Шаблон против библиотеки

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

Включение файла вместо зависимости дает дополнительное преимущество, позволяя легко настраивать его в соответствии с вашими конкретными потребностями (например: добавить ведение журнала или более сложное промежуточное ПО…)

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

С другой стороны, распространение в виде модуля npm ngx-model обеспечивает стандартный опыт разработчика, когда вы просто устанавливаете пакет и сразу используете его.

Как использовать шаблон угловой модели в вашем проекте

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

Это позволяет вам внедрять ModelFactory в свои сервисы и создавать экземпляры моделей ...

Как использовать модель в своих сервисах

Импортируйте ModelFactory в свой сервис, вставьте его в конструктор с помощью механизма внедрения зависимостей Angular и создайте экземпляр модели.

После этого вы можете предоставить данные модели как переменную с описательным именем (например: this.todos$: Todo[] = this.model.data$) для использования компонентами, которые импортируют сервис. Не забудьте указать класс модели или интерфейс как общий тип для Model и ModelFactory, чтобы получить надлежащую поддержку проверки типов и завершения кода!

Как изменить состояние

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

Затем сервис извлекает внутреннее состояние модели, изменяет его и устанавливает обратно в модель. Установка состояния автоматически подталкивает новое состояние модели ко всем подписанным компонентам и службам через RxJS Observable. Модель клонирует каждое новое обновление состояния по умолчанию, чтобы гарантировать неизменность данных и предотвратить случайные мутации компонентов.

Как мутировать состояние по событиям из разных источников

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

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

Как отображать состояние в компонентах

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

Мы можем подписаться на данные нашей модели непосредственно в шаблоне компонента, используя собственный канал | async Angular (преимущество которого заключается в автоматическом отказе от подписки при уничтожении компонента).

Асинхронный конвейер лучше всего работает со структурами шаблонов, такими как *ngFor или *ngIf, которые позволяют нам сохранять разрешенный результат в некоторой локальной переменной, чтобы можно было безопасно получать доступ к данным без чрезмерной зависимости от оператора безопасной навигации — (todosService.counts$ | async)?.done) или необходимости подписки (используйте асинхронный канал) несколько раз, чтобы получить доступ ко всем свойствам модели.

Иногда нам лучше подписаться на данные нашей модели явно в одном из методов компонента (чаще всего ngOnInit), используя .subscribe() и сохраняя данные в одной из переменных компонента. Это позволяет нам снова использовать стандартную привязку данных шаблона Angular ...

Как инициализировать состояние перед переходом маршрута на основе параметров маршрута

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

Шаблон угловой модели использует свойство resolve несколько по-другому, чтобы добиться инициализации модели до завершения перехода маршрута. Наш сервис реализует интерфейс Resolver<boolean>, что означает, что он возвращает только логический флаг (true) для маршрута и вместо этого использует разрешенные данные для инициализации своей модели. Это оставляет нам гибкость для реализации любой стратегии восстановления в случае сбоя инициализации модели как части этого метода. Выброс исключения в методе разрешения по умолчанию приведет к прерыванию перехода активного маршрута.

Затем компонент использует модель обслуживания как обычно, используя канал this.todoService.todos$ | async в шаблоне или явную подписку this.todoService.todos$.subscribe(/* ... */).

Как объединить состояние из нескольких сервисов

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

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

Как организовать зависимую мутацию состояния в нескольких сервисах

Иногда реализация бизнес-требований может привести к необходимости обновления нескольких моделей и выполнения побочных эффектов в четко определенной последовательности. В этом случае может быть очень полезно реализовать «службы приложений» только для оркестровки - термин, заимствованный из литературы по DDD (проектированию на основе предметной области).

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

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

Сравнение с популярными государственными библиотеками, такими как ngRx или Redux

Большинству из нас известны отличные библиотеки управления состоянием, такие как ngRx, Reduxangular-redux), которые обеспечивают хороший стандартизированный способ управления вашим состоянием, определяя такие концепции, как действия, редукторы, эффекты, диспетчер, магазин, селекторы

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

Actions

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

Reducers & Effects

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

Dispatcher

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

Store & Selectors

Модель предоставляет данные как RxJS Observable через свойство .data$. Затем служба повторно предоставляет данные модели с описательно названным свойством (например, this.todo$ = this.model.data$), которые затем используются компонентами и другими службами.

Вы также можете просто предоставить производные данные (например: this.todosCount$ = this.model.data$.map(todos => todos.length))

Варианты использования, которые лучше подходят для ngRx, Redux,…

Большое спасибо Грегу Локвуду за отличный обзор вариантов использования, которые лучше подходят для более сложных библиотек.

При выборе этого варианта или полномасштабного Redux / @ngrx следует иметь в виду одну вещь: нужны ли вам в настоящее время или в будущем некоторые другие функции, которые предоставляет централизованное управление состоянием.

Например:

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

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

На сегодня все!

Посетите веб-сайт документации и поддержите эту статью своим 👏 👏, чтобы распространить ее среди более широкой аудитории!

Также не стесняйтесь проверять другие интересные посты, связанные с интерфейсом, например…







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

И никогда не забывай, будущее светлое