Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Angular — один из самых масштабных из существующих веб-фреймворков. Он включает в себя множество встроенных возможностей. А это значит, что для полноценного освоения Angular нужно разобраться с изрядным количеством концепций.
Автор материала, перевод которого мы сегодня публикуем, полагает, что существует шесть концепций, глубокое знание которых нужно Angular-разработчикам для того чтобы создавать хорошо спроектированные приложения. При этом он говорит не об изучении исходного кода реализации этих концепций, хотя и ему самому иногда приходится заглядывать в код. Речь идёт о понимании соответствующих механизмов и об умении применять их на практике.
В мире веб-разработки модульная архитектура Angular — это нечто особенное. Вероятно, это одна из таких идей, которые хуже других усваиваются новичками.
Самое сложно здесь заключается в том, что в веб-разработке уже используется модульная архитектура. Я, конечно, говорю об ES6-импортах.
Так как Angular-модули добавляют в систему дополнительный уровень логической группировки, важно, чтобы их структура как можно лучше соответствовала решаемым с их помощью задачам.
Знание о том, как разделять и объединять функционал приложения, пользуясь качественно спроектированными модулями, это фундаментальная часть создания архитектуры Angular-приложений.
Существуют различные типы Angular-модулей, о которых нужно знать:
Вот материал, в котором можно найти подробности об Angular-модулях.
Я сказал бы, что вышеозначенное различие между модулями можно распространить и на библиотеки. При таком подходе окажется, что может существовать библиотека, содержащая только сервисы, библиотека, представляющая маршрут, и так далее.
Но то, создают ли модуль или библиотеку, сильно зависит от типа проекта, и от того, представлен ли проект монорепозиторием или несколькими репозиториями.
Вот несколько вопросов, которыми стоит задаться перед написанием модуля:
Разделение ответственности — это, в теории, просто. А вот на практике это уже сложнее. Разработчики, со времён Angular.js, знали о том, что компоненты надо делать как можно компактнее, а сервисы стоит делать более масштабными. В новых версиях Angular эти идеи не претерпели особых изменений.
И в наши дни важно иметь представление о том, что именно должно входить в состав компонентов, что — в состав сервисов, а также учитывать то, что директивы, возможно, являются весьма сильно недооценённой возможностью Angular.
Ответ на вопрос о том, где именно хранить состояние компонента, зависит от того, где нужны соответствующие данные. А именно, они могут быть нужны только в компоненте, являясь локальными и инкапсулированными, или они могут быть нужны за пределами компонента.
Вероятно, большинство манипуляций с DOM должно выполняться в директивах. Представим, что один из компонентов оснащают Drag-and-Drop-функционалом.
Уверен, вы в такой ситуации сможет создать компонент и осуществить привязку соответствующих событий из него, но, если так и сделать, будут смешаны два явления:
Директивы — это возможность Angular, позволяющая описывать механизмы, предназначенные для многократного использования. Почти в каждом проекте, над которым мне довелось работать, я замечал недостаточно широкое использование директив. Директивы могут взять на себя немалую долю ответственности компонентов.
Вот вам упражнение: найдите в своём текущем проекте самый большой, по количеству строк кода, компонент. Используется ли в нём
Когда речь идёт о повторном рендеринге пользовательского интерфейса, то в Angular всё делается как по волшебству, с использованием внутренних механизмов фреймворка.
Но вот если нужно оптимизировать приложение так, чтобы повторный рендеринг интерфейса выполнялся бы только тогда, когда в этом есть необходимость, с этим «волшебством» приходится разбираться. А, улучшая рендеринг, приходится полагаться не только на знания, но и на интуицию.
Архитектору Angular-приложения, вероятно, стоит знать о том, что для оптимизации производительности рендеринга применяется стратегия обнаружения изменений
Для того чтобы улучшить процесс обнаружения изменений, используемый в проекте, есть смысл начать со следующих идей:
Если вы стремитесь к разработке высокопроизводительных Angular-приложений — вам просто необходимо очень хорошо разобраться с вопросами обнаружения изменений. Дело в том, что высокая производительность — это даже не «обновление интерфейса тогда, когда это нужно». Это — «обновление интерфейса только тогда, когда это нужно».
Уменьшение количества повторных рендерингов интерфейса приложения — это один из секретов, позволяющих создавать быстрые и эффективные приложения. Но иногда производительность приложений должна выходить за границы, определяемые самим устройством Angular. Среди таких приложений можно отметить игры, проекты, данные которых часто обновляются, страницы, выводящие большие и сложные списки, и так далее.
Если вам и правда надо выжать из Angular абсолютный максимум производительности, это значит, что вам стоит прибегнуть к методике, предусматривающей избавление от Zone.js и точное обновление интерфейса с использованием свежих возможностей Ivy. Вот материал об этом.
Маршрутизация — это не только представление SPA в виде множества виртуальных страниц. Это ещё и загрузка бандлов приложения по запросу с использованием возможностей по ленивой загрузке материалов подсистемы маршрутизации Angular.
Если вы работаете над большим приложением и размеры бандлов этого приложения превышают 1 Мб, то вы, вероятно, уже знаете о том, почему это важно. И правда, никому не покажется заманчивой перспектива загрузки огромных объёмов данных для того чтобы поработать с неким приложением.
Маршрутизацию стоит использовать не только для того чтобы разделять маршруты верхнего уровня, но и для организации работы с более мелкими и глубокими частями интерфейса.
Это позволяет разбивать содержимое бандлов по основным маршрутам и помогает разделять приложения на небольшие части, которые не нужно передавать пользователям до тех пор, пока не будет сделан явный запрос на их загрузку.
Предположим, что мы разрабатываем пользовательский интерфейс, в котором используются вкладки. При этом каждая вкладка независима от других. Это — идеальная ситуация, в которой каждой вкладке можно назначить собственный маршрут и организовать ленивую загрузку данных, в ходе которой клиенту передаются только данные выбранной им вкладки.
Хотите ещё пример? Как насчёт всплывающих и модальных окон? Их код совершенно не нужно включать в состав материалов, входящих в первоначально загружаемый бандл проекта. Код таких окон есть смысл загружать только тогда, когда они нужны, но не раньше.
Если вам хочется, перед применением подобных идей, чем-то вдохновиться, предлагаю взглянуть на документацию компонента @angular/material/tabs, в котором реализован вышеописанный паттерн.
Большинство CRUD-приложений, в сущности, созданы из множества форм. Весьма вероятно то, что вы тратите очень много времени, создавая формы. Поэтому тому, кто хочет быть Angular-архитектором, важно как следует освоить работу с формами.
Большинство ваших форм, вероятно, будет использовать модуль
API Angular, предназначенный для работы с формами, довольно просто освоить. Для того чтобы достичь совершенства в использовании этого API, в общем-то, достаточно как следует изучить документацию и знать о том, какие проблемы могут возникать при работе с формами.
Главная проблема, о которой стоит знать, заключается в том, что формы в Angular не привязаны к типам данных, которые лежат в их основе. Это, вероятно, самое неприятное в работе с механизмами, которые, в остальном, сделаны очень хорошо. В результате оказывается, что разработчику нужно тщательно следить за тем, чтобы формы соответствовали структурам данных, которые используются при работе с ними.
И последней в нашем списке, хотя — не последней по значимости, идёт технология RxJS.
Я убеждён в том, что одной из самых мощных возможностей Angular является глубокая интеграция этого фреймворка с Rx и с функциональным реактивным программированием.
Для того чтобы по-настоящему хорошо освоить Angular, открыв дорогу к проектированию качественных приложений, сначала нужно изучить Rx, или, по меньшей мере, самые важные операторы. Сложно быть по-настоящему продвинутым Angular-разработчиком, не потратив немало часов на то, чтобы понять Rx.
У того факта, что изучение Rx помогает в разработке Angular-приложений, есть две причины: производительность и асинхронная обработка данных.
Асинхронная обработка данных — это особенно сложная задача в современных, высокоинтерактивных приложениях. Поэтому стоит забыть о промисах, о
Ещё одна серьёзная причина изучения Rx заключается в оптимизации производительности приложений. Конечно, для начала достаточно использовать асинхронные пайпы, но иногда этого недостаточно. Управлять повторным рендерингом компонентов, например, можно, пропуская через пайп только те события, возникновение которых подразумевает необходимость в повторном рендеринге.
Rx даёт разработчику множество операторов, которые способны помочь ему в кэшировании чего-либо, или в сборке чего-либо в пакеты. А это, как результат, ведёт к оптимизации производительности приложений. Вот материал о паттернах RxJS.
Я привёл здесь небольшой список тем, которые стоит изучить тому, кто стремится стать высокоэффективным Angular-разработчиком, или тому, кто хочет быть архитектором Angular-приложений.
В этот список можно добавить ещё очень много всего. Но, кроме прочего, предлагаю не забывать о том, что для того чтобы по-настоящему хорошо изучить что-то, относящееся к миру веб-разработки, нужно начинать с основ. Это — JavaScript, CSS, паттерны проектирования, методики написания чистого кода, инструментарий и многое другое.
А что бы вы посоветовали изучить тем, кто хочет научиться проектировать качественные Angular-приложения?
Автор материала, перевод которого мы сегодня публикуем, полагает, что существует шесть концепций, глубокое знание которых нужно Angular-разработчикам для того чтобы создавать хорошо спроектированные приложения. При этом он говорит не об изучении исходного кода реализации этих концепций, хотя и ему самому иногда приходится заглядывать в код. Речь идёт о понимании соответствующих механизмов и об умении применять их на практике.
1. Архитектура, модули и библиотеки
В мире веб-разработки модульная архитектура Angular — это нечто особенное. Вероятно, это одна из таких идей, которые хуже других усваиваются новичками.
Самое сложно здесь заключается в том, что в веб-разработке уже используется модульная архитектура. Я, конечно, говорю об ES6-импортах.
Так как Angular-модули добавляют в систему дополнительный уровень логической группировки, важно, чтобы их структура как можно лучше соответствовала решаемым с их помощью задачам.
Знание о том, как разделять и объединять функционал приложения, пользуясь качественно спроектированными модулями, это фундаментальная часть создания архитектуры Angular-приложений.
▍Разные типы Angular-модулей
Существуют различные типы Angular-модулей, о которых нужно знать:
- Declarations/Widget Module. Модули с объявлениями различных сущностей. В качестве примера подобных модулей можно привести наборы компонентов пользовательского интерфейса, директив, пайпов.
- Services Module. Модули сервисов. Например —
HttpClientModule
. - Routing Module. Модули маршрутизации.
- Domain Feature Module. Модули, реализующие ключевые задачи приложения.
- Core/Shared Module. Core-модуль — это модуль для объявления глобальных сервисов. Shared-модуль — это модуль, в котором объявляют компоненты для совместного использования.
Вот материал, в котором можно найти подробности об Angular-модулях.
▍Библиотека или модуль?
Я сказал бы, что вышеозначенное различие между модулями можно распространить и на библиотеки. При таком подходе окажется, что может существовать библиотека, содержащая только сервисы, библиотека, представляющая маршрут, и так далее.
Но то, создают ли модуль или библиотеку, сильно зависит от типа проекта, и от того, представлен ли проект монорепозиторием или несколькими репозиториями.
▍Вопросы, которые следует задать себе перед созданием модуля
Вот несколько вопросов, которыми стоит задаться перед написанием модуля:
- К какой именно разновидности модулей относится создаваемый модуль? Если вы не можете ответить на этот вопрос — это значит, что вам нужно поближе познакомиться с вышеперечисленными типами модулей. Весьма вероятно то, что при ответе на этот вопрос придётся упомянуть один или два типа модулей. В частности, речь идёт о модулях маршрутизации и модулях сервисов.
- Нужно ли оформить этот модуль в виде библиотеки, или он может быть обычным модулем? Ответ на этот вопрос найти немного сложнее. Я полагаю, что если используется монорепозиторий, то имеет смысл ориентироваться на разработку библиотек. Это, в долгосрочной перспективе, себя оправдает.
2. Разделение ответственности между компонентами, сервисами и директивами
Разделение ответственности — это, в теории, просто. А вот на практике это уже сложнее. Разработчики, со времён Angular.js, знали о том, что компоненты надо делать как можно компактнее, а сервисы стоит делать более масштабными. В новых версиях Angular эти идеи не претерпели особых изменений.
И в наши дни важно иметь представление о том, что именно должно входить в состав компонентов, что — в состав сервисов, а также учитывать то, что директивы, возможно, являются весьма сильно недооценённой возможностью Angular.
▍Состояние
Ответ на вопрос о том, где именно хранить состояние компонента, зависит от того, где нужны соответствующие данные. А именно, они могут быть нужны только в компоненте, являясь локальными и инкапсулированными, или они могут быть нужны за пределами компонента.
- Если компоненты совместно используют состояние, или к состоянию нужно обращаться из сервисов, тогда состояние стоит хранить в сервисе. При этом, если состояние хранится в сервисе, не играет особой роли то, какие именно инструменты управления состоянием используются.
- Если состояние является локальным (например — речь идёт о форме) и используется только внутри компонента, тогда состояние стоит просто сохранить в компоненте.
▍Работа с DOM
Вероятно, большинство манипуляций с DOM должно выполняться в директивах. Представим, что один из компонентов оснащают Drag-and-Drop-функционалом.
Уверен, вы в такой ситуации сможет создать компонент и осуществить привязку соответствующих событий из него, но, если так и сделать, будут смешаны два явления:
- Описание внешнего вида компонента.
- Определение поведения компонента.
Директивы — это возможность Angular, позволяющая описывать механизмы, предназначенные для многократного использования. Почти в каждом проекте, над которым мне довелось работать, я замечал недостаточно широкое использование директив. Директивы могут взять на себя немалую долю ответственности компонентов.
Вот вам упражнение: найдите в своём текущем проекте самый большой, по количеству строк кода, компонент. Используется ли в нём
Renderer
или ElementRef
? Соответствующая логика, скорее всего, может быть перенесена в директиву.3. Обнаружение изменений и рендеринг
Когда речь идёт о повторном рендеринге пользовательского интерфейса, то в Angular всё делается как по волшебству, с использованием внутренних механизмов фреймворка.
Но вот если нужно оптимизировать приложение так, чтобы повторный рендеринг интерфейса выполнялся бы только тогда, когда в этом есть необходимость, с этим «волшебством» приходится разбираться. А, улучшая рендеринг, приходится полагаться не только на знания, но и на интуицию.
Архитектору Angular-приложения, вероятно, стоит знать о том, что для оптимизации производительности рендеринга применяется стратегия обнаружения изменений
onPush
. Но в ходе работы всё не всегда идёт так, как ожидается. Особенно тогда, когда в шаблонах не используют наблюдаемые объекты и асинхронные пайпы.▍Совершенствование обнаружения изменений
Для того чтобы улучшить процесс обнаружения изменений, используемый в проекте, есть смысл начать со следующих идей:
- Нужно рассматривать все данные как иммутабельные. Здесь могут очень пригодиться библиотеки для управления состоянием, основанные на Rx.
- Для вывода данных в шаблонах стоит использовать только (или преимущественно) наблюдаемые объекты. При использовании локального состояния стоит применять
BehaviorSubject
.
Если вы стремитесь к разработке высокопроизводительных Angular-приложений — вам просто необходимо очень хорошо разобраться с вопросами обнаружения изменений. Дело в том, что высокая производительность — это даже не «обновление интерфейса тогда, когда это нужно». Это — «обновление интерфейса только тогда, когда это нужно».
▍Преодоление ограничений производительности Angular
Уменьшение количества повторных рендерингов интерфейса приложения — это один из секретов, позволяющих создавать быстрые и эффективные приложения. Но иногда производительность приложений должна выходить за границы, определяемые самим устройством Angular. Среди таких приложений можно отметить игры, проекты, данные которых часто обновляются, страницы, выводящие большие и сложные списки, и так далее.
Если вам и правда надо выжать из Angular абсолютный максимум производительности, это значит, что вам стоит прибегнуть к методике, предусматривающей избавление от Zone.js и точное обновление интерфейса с использованием свежих возможностей Ivy. Вот материал об этом.
4. Маршрутизация
Маршрутизация — это не только представление SPA в виде множества виртуальных страниц. Это ещё и загрузка бандлов приложения по запросу с использованием возможностей по ленивой загрузке материалов подсистемы маршрутизации Angular.
Если вы работаете над большим приложением и размеры бандлов этого приложения превышают 1 Мб, то вы, вероятно, уже знаете о том, почему это важно. И правда, никому не покажется заманчивой перспектива загрузки огромных объёмов данных для того чтобы поработать с неким приложением.
Маршрутизацию стоит использовать не только для того чтобы разделять маршруты верхнего уровня, но и для организации работы с более мелкими и глубокими частями интерфейса.
Это позволяет разбивать содержимое бандлов по основным маршрутам и помогает разделять приложения на небольшие части, которые не нужно передавать пользователям до тех пор, пока не будет сделан явный запрос на их загрузку.
▍Пример: компонент с вкладками
Предположим, что мы разрабатываем пользовательский интерфейс, в котором используются вкладки. При этом каждая вкладка независима от других. Это — идеальная ситуация, в которой каждой вкладке можно назначить собственный маршрут и организовать ленивую загрузку данных, в ходе которой клиенту передаются только данные выбранной им вкладки.
Хотите ещё пример? Как насчёт всплывающих и модальных окон? Их код совершенно не нужно включать в состав материалов, входящих в первоначально загружаемый бандл проекта. Код таких окон есть смысл загружать только тогда, когда они нужны, но не раньше.
Если вам хочется, перед применением подобных идей, чем-то вдохновиться, предлагаю взглянуть на документацию компонента @angular/material/tabs, в котором реализован вышеописанный паттерн.
5. Формы
Большинство CRUD-приложений, в сущности, созданы из множества форм. Весьма вероятно то, что вы тратите очень много времени, создавая формы. Поэтому тому, кто хочет быть Angular-архитектором, важно как следует освоить работу с формами.
Большинство ваших форм, вероятно, будет использовать модуль
ReactiveFormsModule
. А если они не состоят из единственного элемента управления, то в них, с использованием ngModel
, будет реализована двусторонняя привязка данных.API Angular, предназначенный для работы с формами, довольно просто освоить. Для того чтобы достичь совершенства в использовании этого API, в общем-то, достаточно как следует изучить документацию и знать о том, какие проблемы могут возникать при работе с формами.
Главная проблема, о которой стоит знать, заключается в том, что формы в Angular не привязаны к типам данных, которые лежат в их основе. Это, вероятно, самое неприятное в работе с механизмами, которые, в остальном, сделаны очень хорошо. В результате оказывается, что разработчику нужно тщательно следить за тем, чтобы формы соответствовали структурам данных, которые используются при работе с ними.
6. RxJS
И последней в нашем списке, хотя — не последней по значимости, идёт технология RxJS.
Я убеждён в том, что одной из самых мощных возможностей Angular является глубокая интеграция этого фреймворка с Rx и с функциональным реактивным программированием.
Для того чтобы по-настоящему хорошо освоить Angular, открыв дорогу к проектированию качественных приложений, сначала нужно изучить Rx, или, по меньшей мере, самые важные операторы. Сложно быть по-настоящему продвинутым Angular-разработчиком, не потратив немало часов на то, чтобы понять Rx.
У того факта, что изучение Rx помогает в разработке Angular-приложений, есть две причины: производительность и асинхронная обработка данных.
Асинхронная обработка данных — это особенно сложная задача в современных, высокоинтерактивных приложениях. Поэтому стоит забыть о промисах, о
setTimeout
и о setInterval
, и начать работать в стиле Rx.Ещё одна серьёзная причина изучения Rx заключается в оптимизации производительности приложений. Конечно, для начала достаточно использовать асинхронные пайпы, но иногда этого недостаточно. Управлять повторным рендерингом компонентов, например, можно, пропуская через пайп только те события, возникновение которых подразумевает необходимость в повторном рендеринге.
Rx даёт разработчику множество операторов, которые способны помочь ему в кэшировании чего-либо, или в сборке чего-либо в пакеты. А это, как результат, ведёт к оптимизации производительности приложений. Вот материал о паттернах RxJS.
Итоги
Я привёл здесь небольшой список тем, которые стоит изучить тому, кто стремится стать высокоэффективным Angular-разработчиком, или тому, кто хочет быть архитектором Angular-приложений.
В этот список можно добавить ещё очень много всего. Но, кроме прочего, предлагаю не забывать о том, что для того чтобы по-настоящему хорошо изучить что-то, относящееся к миру веб-разработки, нужно начинать с основ. Это — JavaScript, CSS, паттерны проектирования, методики написания чистого кода, инструментарий и многое другое.
А что бы вы посоветовали изучить тем, кто хочет научиться проектировать качественные Angular-приложения?