Создание Angular 2+ компонентов с возможностью переключения темы

в 19:27, , рубрики: AngularJS, css, метки: , , , , ,

Всем привет.
Итак, допустим мы пишем сайт, на котором нужно реализовать возможность динамического переключения настроек внешнего вида, или, проще говоря, темы. Темой (theme) будем называть набор свойств, определяющих внешний вид компонентов (да и вообще всего сайта).
Допустим, у нас есть одностраничное приложение на Angular, и пусть в нем будет ооочень много компонентов, и один из них — ButtonComponent (к компоненту подключим стили из button.component.css), на примере которого и рассмотрим весь механизм. И нужно реализовать возможность переключения между двумя темами: «dark» и «light», которые у нас будут отличаться только цветами (а в общем случае можно выносить в тему что угодно, размеры там, шрифты, картинки бэкграунда и т.п. — все, чем можно управлять из css).

Исходные стили «light» выглядели так, например:

.my-button {
  padding: 10px;
  font-size: 14px;
  color: midnightblue;
  border: 1px solid mdnightblue;
  background: white;
  cursor: pointer;
}

my-button:disabled {
  pointer-events: none;
  cursor: default;
  color: grey;
  border: 1px solid grey;
}

А для «dark» вот так:

.my-button {
  padding: 10px;
  font-size: 14px;
  color: lightblue;
  border: 1px solid lightblue;
  background: midnightblue;
  cursor: pointer;
}

.my-button:disabled {
  pointer-events: none;
  cursor: default;
  background: grey;
  color: lightgrey;
  border: 1px solid lightgrey;
}

Выделим общую часть и сохраним ее в отдельный файл. Пусть он лежит там же, в папке с нашим компонентом, и называется button-core.component.css. Так же создадим по одному файлу для каждой темы, назовем их соответственно button-light.component.css и button-dark.component.css. Уже понятно, что в них будет, но для полноты все же приведу код (кто не хочет его видеть — смело скролльте):

button-core.css:

.my-button {
  padding: 10px;
  font-size: 14px;
  cursor: pointer;
}

.my-button:disabled {
  pointer-events: none;
  cursor: default;
}

button-light.css:

.my-button {
  color: midnightblue;
  border: 1px solid mdnightblue;
  background: white;
}

my-button:disabled {
  color: grey;
  border: 1px solid grey;
}

button-dark.css:

.my-button {
  color: lightblue;
  border: 1px solid lightblue;
  background: midnightblue;
}

.my-button:disabled {
  background: grey;
  color: lightgrey;
  border: 1px solid lightgrey;
}

И в конце концов, сам button.component.css будет выглядеть вот так:

@import 'button-core.css'
@import 'button-light.css'
@import 'button-dark.css'

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

А теперь посмотрим, как же мы можем переключиться с одной темы на другую, в том числе из кода.
Для этого нам понадобится селектор :host-context(). Что он делает? Этот селектор ищет определенный css — класс, пробегая по всем родителям элемента, до самого корня. И если он этот класс встречает, то применяет описанные изменения. А лучше немного отвлечемся от нашей кнопки и рассмотрим его работу на примере. Вот пусть у нас есть два вложенных компонента: parent-component и child-component. Стили их изолированы друг от друга, спасибо Angular, и это хорошо, но мы хотим иногда изменять вид child-component в зависимости, скажем, от того, наведен ли курсор на parent-component (событие hover). Можно было бы, конечно, убрать инкапсуляцию при помощи ViewEncapsulation: none, или завести в child-component для этих целей Input — переменную, но намного проще и правильней поступить так:

:host-context(.parent-component:hover) .child-component {
  background: lightcoral;
}

Вернемся к нашей кномке. Можно в качестве маркера для хост-контекст поставить какой-нибудь стиль (:host-context(.some-style)), что нам и нужно сделать. Теперь файлы light-темы будет выглядеть так:
button-light.css:

:host-context(.light-theme) .my-button {
  color: midnightblue;
  border: 1px solid mdnightblue;
  background: white;
}

:host-context(.light-theme) my-button:disabled {
  color: grey;
  border: 1px solid grey;
}

Для dark-theme аналогично.
Что мы получили? Теперь, стоит нам только у одного из родительских элементов нашей кнопки установить класс light-theme или dark-theme, как она изменит свой вид, подключив соответствующие классы из наших файлов. Чаще всего, тема применяется сразу ко всему приложению, а это значит, что переключать тему стоит на корневом элементе, например на внешнем div из app-component (ну или того компонента, который вы прописываете в bootstrap вашего приложения). Можно завести в нем флажок theme и на внешний элемент навесить
[ngClass]="{'light-theme': theme === 'light', 'dark-theme': theme === 'dark'}".
Ну да что уже дальше рассказывать, вроде и так все ясно.

Вот такой простенький способ создания тем без использования css — препроцессоров. Способ несовершенен, т.к. предполагает много копипасты. С препроцессорами типа sass возможно более лаконичное создание тем. Однако, этот способ тоже имеет право на жизнь.
Всем спасибо за внимание, надеюсь, кому-нибудь это окажется полезным.
За основу взята статья anasfirdousi.com/creating-theamable-components-in-angular.html, все идеи, в общем то, оттуда.

Автор: человек со стажем

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js