Angular

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

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

Итак, у нас есть три причины придерживаться принципов данного руководства. И вот почему: 

  1. Этим принципам следуют многие приложения. Это значит, что если вы придерживаетесь этого руководства, то у новых членов вашей команды не будет проблем с вовлечением в совместный процесс разработки, так как у них, скорее всего, уже был когда-то опыт работы с ними. Это особенно актуально для команд с высокой текучестью кадров по тем или иным причинам.
  2. Это руководство по стилю было разработано совместными усилиями экспертов в области ПО, поэтому за простым утверждением “напишите/сделайте X ТАКИМ образом”, стоят огромный опыт и знания
  3. Angular CLI, линтер и многие другие инструменты Angular уже изначально в вашем наборе разработчика, а это значит лишь одно — отсутствие дополнительной головной боли, пользовательских генераторов и зря потраченного времени на изобретение колеса. 

? Core Module

Модуль core должен содержать код, во-первых, конкретный для вашего приложения, а во-вторых, выполняющий сквозные задачи приложений. Другими словами, это “система водоснабжения, электричество и отопление” вашего дома, если сравнивать создание приложения со строительством. Такой код может быть:

  • пользовательским обработчиком ошибок;
  • HTTP перехватчиком для аутентификационных заголовков и заголовков, относящихся к среде;
  • Сервисом, обертывающие основные API Angular, такие как HttpClient. Данный метод часто используется для уменьшения воздействия изменений интерфейса Angular в будущем;
  • Сервисом логирования. 
Сквозные задачи. На примере кода code.reaper12 from edureka!

В заключении раздела отметим один важный момент. Модуль Core следует импортировать только в корневой модуль Angular c именем AppModule или любым другим.

?‍♀️ Core Module Input Guard

Напомню еще раз: “модуль Core следует импортировать только в корневой модуль Angular”.

Простой способ реализации этого правила — добавить так называемый Import Guard в код вашего класса CoreModule. Ниже приведен образец реализации повторно используемого класса Import Guard:

/**
 * Этот абстрактный класс используется для создания модуля через расширение этого класса;
 * Препятствует импорту модуля в какое-либо другое место,кроме корневого модуля приложения. 
 */
export abstract class EnsureImportedOnceModule {
    protected constructor(targetModule: any) {
        if (targetModule) {
            throw new Error(`${targetModule.constructor.name} has already been loaded.`);
        }
    }
}

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

@NgModule({
    declarations: [...],
    imports: [...],
    exports: [...],
    entryComponents: [...],
    providers: [...]
})
export class CoreModule extends EnsureImportedOnceModule {
    public constructor(@SkipSelf() @Optional() parent: CoreModule) {
        super(parent);
    }
}

Аннотация @SkipSelf гарантирует, что родительский модуль модуля, использующего CoreModule, внедряется Angular DI в конструктор CoreModule, в то время как аннотация@Optional замалчивает ошибки и вынуждает Angular DI предоставлять null в случае, если модуль недоступен, (т.е. модуль, в который был импортирован CoreModule, находится на вершине дерева иерархии модулей Angular). Все это означает, что:

  • значение, внедренное в параметр parent , не является null;
  • модуль не был импортирован в корневой модуль приложения; 
  • код выбросит исключение среды выполнения.

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

? Shared Module

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

Модуль shared, в отличие от core, можно многократно импортировать в разные модули внутри приложения.

Сравнение структуры импорта модулей Shared и Core

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

Стоп… Модуль Core без компонентов?

Именно в связи с компонентами возникает камень преткновения во всем процессе выбора между модулями core и shared. Конечно же, в целом компоненты по своей природе предназначены для повторного использования, но будут ли они использованы повторно на самом деле? Задумайтесь над этим вопросом. Навигационные панели, футеры (нижние блоки сайта), макеты этих панелей и футеров, боковые меню и т.д., вероятнее всего, не будут применяться повторно, хотя все они являются компонентами.

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

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

В каком же направлении двигаться — к модулю Feature или Shared?

Если вы задаетесь этим вопросом, то следует принять во внимание следующие моменты: 

  1. Какую методологию разработок мы используем? 
  2. Соответствует ли мое решение правилам ревью нашей команды? 

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

Работая в негибкой среде, вам определенно следует прогнозировать возможные варианты изменения вашей системы. Например, учесть технические нюансы: кто и как будет интегрировать, какие модули будут интегрированы, стоит ли предоставить библиотеку или виджет с поддержкой ifame и т. д. Не забудьте и про функциональные нюансы, спросите себя: “Как изменится мое видение относительно применения моего ПО в ближайшем будущем?”. Если сейчас вам трудно ответить на все эти вопросы, то всегда можно вернуться к ним на последнем этапе проектирования ПО. 

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

? Заключение 

Итак, вы познакомились с несколькими полезными советами о размещении кода в модулях core/ shared приложения Angular. На практике ваши решения о размещении кода в том или ином модуле системы могут быть неоднозначными.

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

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

  • Если это код только для вашего приложения (навигационные панели, футеры и т.д.), и у него нет внутрипроектных зависимостей от других модулей, выбирайте — core.
  • Если это код, обеспечивающий связи между внутренними компонентами приложения, например, обертки основных API Angular, обработчики ошибок, HTTP перехватчики, решение в пользу — core
  • Если, исходя из задач кода, предполагается его повторное использование во многих местах приложения, выбор очевиден — shared
  • Если в вашем случае правила вступают в противоречие, то стоит обсудить с членами команды. 

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


Перевод статьи Radek Busa: “Where Shall I Put That?” — Core vs Shared Module in Angular