Banx. Организация стилей в Angular

Aleksandr Serenko
F.A.F.N.U.R
Published in
7 min readMar 28, 2021

--

Banx. Организация стилей в Angular в монорепозитории Nx

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

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

Стили в Angular

Angular из коробки поддерживает CSS, а также следующие препроцессоры SASS, Stylus.

В зависимости от выбранного типа, angular приложение будет иметь основной файл стилей в папке src — styles.css (.scss, .styl). Данный файл будет содержать все общие стили для всего приложения и он подключается в index.html, поэтому в styles.css должен содержать только самые необходимые стили, все остальное должно располагаться либо внутри компонентов, либо подключаться непосредственно в компонентах.

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

  • Режим Emulated будет добавлять ко всем селекторам css суффикс компонента, что позволит персонализировать стили для конкретного компонента. Например, app-component, который содержит стили для абзацев p { font-size: 18px; }, после сброки приложения трансформируется в селектор p[_app-component_1] {font-size: 18px;}, к которому будет добавлен суффикс согласно построенному DOM.
  • Режим None — отключает инкапсуляцию, и все стили будут применяться для всех указанных селекторов.
  • Shadow Dom — Angular будет стараться использовать современный стандарт для создания стилей с host.

Глобальные стили

Глобальные стили — стили, которые применяются для всего приложения. Они обычно содержат базовые значения для html, body, а также приводят к единому стилю все HTML элементы, задавая максимальную схожесть во всех современных браузерах. Также глобальные стили содержат переменные и миксины, которые могут быть использованы в проекте.

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

  • Создается папка styles;
  • В папке создаются файлы variables, utils, mixins, которые содержат основные переменные и миксины проекта;
  • Также обычно создается (или не создается) папка, в которой будут “common” стили для компонентов Angular;
  • Все стили подключаются в styles.scss.

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

Заметим, что глобальные стили сводятся к минимуму, и подключаются непосредственно в styles.scss внутри самого проекта. Это необходимо для того, чтобы оптимизировать отрисовку страниц.

Содержимое глобальных стилей

Есть шесть основных пунктов, которые должны быть заданы:

  1. Поставить box-sizing: content-box, для того, чтобы было ожидаемое поведение с шириной элементов
  2. Установить font-family, так как в браузерах разные дефолтные шрифты
  3. Установить line-height
  4. Запретить коррекцию размера шрифта после изменения ориентации
  5. Исправить viewport в IE и Edge
  6. Измените подсветку экрана по умолчанию, чтобы она была полностью прозрачной в iOS

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

Минимальный набор будет примерно таким:

Также можно добавить немного стилей для заголовков, ссылок и выделения, но это все в соответствии с дизайном.

Организация внутренних и общих стилей

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

Подключение стилей внутри angular компонентов

В Angular компоненте подключить стили можно двумя способами:

  • в декораторе компонента;
  • с помощью @import внутри файла стилей.

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

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

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

Проблема общих стилей для компонентов

Для создания общих стилей для нескольких компонентов, есть как минимум два способа:

  • Создание файла общих стилей (feature.scss) и подключение данного файла в каждом используемом компоненте.
  • Создание общего компонента и использование созданного компонента как родительского.

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

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

Допустим есть два компонента (PostsComponent и ReviewsComponent), которые используют общий набор стилей.

И кажется, можно создать файл card.scss и подключить в каждом из компонентов.

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

Одной из самых дорогих операций в рендере Angular является создание Html, причем внутри шаблонов. Чем меньше будет html элементов, тем быстрее будет работать приложение.

Правильным решением будет создание нескольких компонентов: CardComponent, CardTitleComponent и CardContent. И каждый из этих компонентов, будет иметь только свои стили.

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

Стоит отметить, что данная концепция присутствует в Angular Material.

Структура стилей в монорепозитории Nx

Тут важно выделить рекомендации Nx, которые предлагают хранить все библиотеки в папке ui — Using Nx at Enterprises.

Пример подобного использования:

happynrwl/
├── apps/
│ ├── happynrwlapp/
│ ├── happynrwlapp-e2e/
│ ├── adminapp/
│ └── adminapp-e2e/
├── libs/
│ ├── happynrwlapp/
│ │ ├── feature-main/
│ │ ├── ui-table/
│ │ └── utils-testing/
│ ├── adminapp/
│ │ ├── feature-main/
│ │ ├── feature-login/
│ │ ├── ui/
│ │ └── utils-testing/
│ └── shared/
│ ├── ui/
│ └── utils-testing/
├── tools/
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.json
  • happynrwlapp/registration/feature-main- библиотека, которая используется только в одном месте
  • happynrwlapp/shared/ui-общая библиотека для happynrwlapp
  • shared/ui- общая библиотека для любых приложений в монорепозитории

Если немного абстрагироваться, то в более понятной форме можно представить структуру:

my-project
├── apps
│ └── cabinet
├── libs
│ ├── core
│ │ ├── api
│ │ ├── logger
│ │ └── utils
│ ├── login
│ │ ├── api
│ │ ├── guards
│ │ └── page
│ └── ui
│ ├── layout
│ └── styles
├── tools
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.json

Есть приложение — cabinet. Данное приложение использует библиотеки: core/api, core/logger, core/utils. Также в данном приложении есть страница логина(login/page), которая подключает апи (login/api).

Все глобальные стили располагаются в ui/styles. Общим layout’ом является ui/layout, в котором есть шапка, подвал и основной раздел с контентом. Лейаут использует миксины и переменные из ui/styles, просто подключая импортом в нужных компонентах. Например, в шапке будет libs/ui/layout/src/lib/components/header/header.component.scss:

@import 'libs/ui/styles';:host {
padding: 1rem 0;
}

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

my-project
├── apps
│ ├── english
│ │ └── cabinet
│ └── russian
│ └── cabinet
├── libs
│ ├── core
│ │ ├── api
│ │ ├── logger
│ │ └── utils
│ ├── login
│ │ ├── api
│ │ └── guards
│ ├── englush
│ │ ├── login
│ │ │ └── page
│ │ └── ui
│ │ ├── layout
│ │ └── styles
│ └── russian
│ ├── login
│ │ └── page
│ └── ui
│ ├── layout
│ └── styles
├── tools
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.json

Есть два приложения russian/cabinet и english/cabinet. Каждое из приложений имеет свой лейаут и свою папку со стилями. Но для того, чтобы каждая библиотека имела доступ к соответствующей папке стилей, первоначальная страница логина (login/page) стала двумя библиотеками: russian/login/page и english/login/page.

Это вызвано тем, что login/page не знает какие стили ей использовать. Если страницы бы совпадали, то тогда, структура была бы как в первом варианте:

my-project
├── apps
│ ├── english
│ │ └── cabinet
│ └── russian
│ └── cabinet
├── libs
│ ├── core
│ │ ├── api
│ │ ├── logger
│ │ └── utils
│ ├── login
│ │ ├── api
│ │ ├── guards
│ │ └── page
│ ├── englush
│ │ └── ui
│ │ ├── layout
│ │ └── styles
│ ├── russian
│ │ └── ui
│ │ ├── layout
│ │ └── styles
│ └── ui
│ ├── layout
│ └── styles
├── tools
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.json

Теперь страница логина одна, однако, появилась общая папка Ui, которая может быть использована в обоих приложениях.

Universal, prerender и первая загрузка

Последнее о чем стоит поговорить это про Universal.

Если вы используете каких-либо библиотеки (bootstrap, angular-material), то скорее всего они будут подключены глобально. А это значит, что при первой загрузке вы получите “скачек”, когда часть страницы уже отрисована пререндером или universal, но нет пары стилей, которые находятся в styles.css, но файл которых еще не был загружен.

Чтобы избавиться от этого эффекта, нужно хранить глобальные стили на уровне html и отдавать их сразу. Однако, это работает только в том случае, если стилей очень мало. Вы не можете запихнуть весь bootstrap в index.html и отдать его пользователю.

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

Резюме

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

Рассмотрели варианты подключения стилей в компонентах Angular. Поговорили о проблеме общих стилей для компонентов и привели решение с созданием shared компонентов для повторного использования.

Рассмотрели структуру организации папок со стилями в Nx. Привели несколько примеров для разных проектов в монорепозитории.

В конце статьи поговорили о проблемах со стилями в Universal.

Исходники

Все исходники находятся на github, в репозитории:

Для того, чтобы посмотреть состояние проекта на момент написания статьи, нужно выбрать соответствующий тег — styles.

git checkout styles

Ссылки

Подписывайтесь на блог, чтобы не пропустить новые статьи про Angular, веб-разработку и новости из мира фронтенда.

Medium: https://medium.com/fafnur
Добавляйтесь в группу ВК: https://vk.com/fafnur
Добавляйтесь в группу в Fb: https://www.facebook.com/groups/fafnur/
Телеграм канал: https://t.me/f_a_f_n_u_r
Twitter: https://twitter.com/Fafnur1
Instagram: https://www.instagram.com/fafnur
LinkedIn: https://www.linkedin.com/in/fafnur

Предыдущие статьи:

  1. Banx. Создание Nx workspace для Angular.
  2. Banx. Настройка базовых правил в eslint в Nx в Angular
  3. Banx. Структура Angular приложения в монорепозитории Nx

--

--

Aleksandr Serenko
F.A.F.N.U.R

Senior Front-end Developer, Angular evangelist, Nx apologist, NodeJS warlock