Применение шаблона, подобного MVVM, в Angular (часть 1)

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

Каковы наши цели?

  • добиться полного разделения компонентов и бизнес-логики за счет применения MVVM
  • сделать бизнес-уровень легко расширяемым
  • сделать бизнес-уровень легко взаимозаменяемым
  • сделать бизнес-уровень легко тестируемым
  • держите материал пользовательского интерфейса исключительно в компоненте
  • сделать этот шаблон применимым для любого компонента

Перед погружением в…

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

Тем не менее, это также поможет немного познакомиться с MVVM и его значением. Я узнал об этом много лет назад, работая с C# и WPF, в отличной статье, которая доступна и сегодня. Проверьте это: https://www.codeproject.com/Articles/819294/WPF-MVVM-step-by-step-Basics-to-Advance-Level

Вот так:

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

Другой важной частью является наличие отдельной службы, которая обрабатывает всю бизнес-логику компонента. Этот сервис, который мы назовем ui-service, будет манипулировать только моделью представления, и благодаря магии привязки Angular пользовательский интерфейс будет автоматически обновляться. Это означает, что большинство, если не все привязки из нашего представления html будут указывать на наш экземпляр модели представления. Мы должны использовать один и тот же экземпляр для всех частей нашего шаблона.

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

Хорошо, давайте сначала посмотрим, как выглядит наша структура компонента:

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

Что касается папки компонента, мы замечаем классический шаблон html+scss+ts, с которым мы знакомы в Angular. Две дополнительные структуры, которые мы видим, — это модель представления и пользовательский интерфейс.

Мы будем брать файлы один за другим и детализировать их.

(Аннотация) ui-service.ts

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

(Абстрактный) ui-component.ts

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

Вот и все для абстрактных классов. Давайте теперь посмотрим на наши файлы компонентов.

hero.comp.view-model.ts

hero.comp.ui-service.ts

И прилагаемый файл спецификации hero.comp.ui-service.spec.ts

hero.comp.ts

И, наконец, наш hero.comp.html

И это наша начальная установка. Теперь мы можем запустить наше приложение и отобразить наш компонент. Мы можем манипулировать моделью, даже не выходя из ui-сервиса. Мы можем получать данные с сервера или просто выполнять вычисления. Мы можем внедрить другие сервисы и использовать их. Просто не забудьте разделить вещи: бизнес-требования реализуются в ui-service; Требования, связанные с пользовательским интерфейсом, реализуются в компоненте. И помните, вы можете добавить свой бизнес-объект в модель представления, если вам нужно, и иметь такой же уровень доступа, как и для любого другого поля.

Взаимодействие с нашим UI-сервисом

Наша работа не сделана. Мы по-прежнему хотим знать, когда что-то происходит в нашем ui (кнопка нажата или что-то проверяется) и уведомить ui-сервис об этом событии.

Решение, которое мы сейчас применим, очень простое: используйте компонент для прослушивания событий и просто вызывайте методы в ui-сервисе.

Давайте добавим несколько кнопок в наш html:

И добавляем методы в класс нашего компонента:

И, наконец, к нашему ui-сервису:

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

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

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

Во Части 2 этого руководства мы попытаемся немного улучшить наш шаблон, чтобы сделать его еще более гибким, и мы увидим, как мы можем иметь несколько UI-сервисов и сделать их взаимозаменяемыми в зависимости от контекста.