Cómo hacer un tema personalizado en Angular Material

Angular Material es una gran biblioteca que implementa Material Design para Angular 2+. El documento oficial es suficiente con respecto a los usos de los componentes, mientras que hay pocos artículos sobre cómo personalizar el tema en sí, específicamente, los colores utilizados en el tema.

En esta publicación, me gustaría resumir lo que he aprendido estos meses al personalizar los temas de Angular Material.

Tenga en cuenta que este artículo NO trata sobre AngularJS Material, que se utiliza para AngularJS 1.x.

Artículos Relacionados

Algunas publicaciones comunes sobre la personalización de temas son:

  • "Tematización de la aplicación Angular Material", la guía oficial para temas personalizados,
  • "La guía completa de temas de materiales angulares" por Tomas Trajan, que proporciona muchas instrucciones indocumentadas. Muy recomendable .

No encontré otras publicaciones útiles y agradecería si alguien pudiera proporcionar algunos recursos en los comentarios.

Cómo crear un tema personalizado

Crear un tema de material es extremadamente simple: solo necesita elegir tres colores: primario , acento y advertencia , y Angular Material hará el resto por usted. La página de la paleta de materiales explica claramente cómo funciona y también puede crear un tema visualmente con la herramienta Color.

En lo que respecta al código, todo lo que necesita hacer es crear el siguiente archivo de tema:

// [email protected] '[email protected]/material/theming';
$my-theme-primary: mat-palette($mat-green);$my-theme-accent : mat-palette($mat-amber);$my-theme-warn : mat-palette($mat-red);
$my-theme: mat-light-theme( $my-theme-primary, $my-theme-accent, $my-theme-warn);

Entonces necesitas aplicar este tema en tu style.scssarchivo principal :

@import "theme.scss";
@include mat-core();@include angular-material-theme($my-theme);

Cómo utilizar un tema personalizado en los componentes

Después de crear nuestro propio tema, surgirán requisitos como este:

Quiero crear un cuadro de texto. El color del texto, el color de fondo y el color del borde deben provenir de nuestro propio tema, no de una codificación rígida.

Este requisito es bastante común; de todos modos, poder usarse en componentes es exactamente la razón por la que queremos crear un tema personalizado. El problema es cómo.

El enfoque mixin

El primer documento oficial que compartí propuso una forma de usar el mixin de SCSS. Yo lo llamo un enfoque "de abajo hacia arriba", que incluye los siguientes pasos:

  1. Cada componente define una mezcla de tema y recupera colores del $themeparámetro.
  2. Un global theme.scssdefine el tema personalizado, luego incluye todos los mixins de temas de componentes y los llama con el tema personalizado.

Además de la theme.scssdefinición mencionada anteriormente, cada componente necesita crear un archivo de tema como este:

// src/app/comp-a/[email protected] '[email protected]/material/theming';
@mixin comp-a-theme($theme) { // define mixin $primary: map-get($theme, primary); // retrieve color def button { // apply theme to component background-color: mat-color($primary); }}

Y probablemente quieras custom-theme.scssimportar todos los temas de nivel de componente:

// src/app/[email protected] '[email protected]/material/theming';@import 'src/app/comp-a/comp-a.theme';@import 'src/app/comp-b/comp-b.theme';
@mixin custom-themes($theme) { @include comp-a-theme($theme); @include comp-b-theme($theme);}

Luego importe lo anterior custom-theme.scssen su theme.scss:

// [email protected] './custom-theme';@include custom-themes($my-theme);

Esta jerarquía funciona y probablemente sea la única forma en que necesite admitir varios temas .

Sin embargo, la mayoría de las veces solo admitimos un tema, y ​​usar un mixin puede resultar engorroso. Principalmente, hay tres desventajas con este enfoque:

  1. Cada referencia de color necesita un .theme.scssarchivo separado .
  2. custom-theme.scssdebe saber exactamente qué componentes proporcionan temas personalizados. Esto crea dependencias innecesarias.
  3. Lo más importante es que los archivos de tema a nivel de componente no están encapsulados.

Los puntos primero y segundo se explican por sí mismos. Permítanme explicar un poco sobre el punto 3. Esto implica algunos conocimientos previos llamados "Encapsulación de vistas".

Angular utiliza una técnica llamada "Encapsulación de vista" para mantener el CSS del componente local. En otras palabras, las reglas definidas para un componente permanecerán en ese componente y no afectarán a otros componentes.

De esta manera, puede definir el nombre de la clase CSS libremente en su componente sin preocuparse por los conflictos de nombres. Sin embargo, la encapsulación de la vista se realiza solo si el CSS se define mediante @Component, es decir @Component({ styleUrls: ['./comp-a.scss'] }).

En cuanto a nuestro archivo de tema personalizado comp-a.theme.scss, dado que es importado directamente por custom-theme.scss, sus reglas no están encapsuladas, por lo que se aplicará a todos los elementos de la página. En el ejemplo anterior, utilicé el siguiente código (¡que estaba INCORRECTO!):

@mixin comp-a-theme($theme) { button { ... } // This will apply to ALL buttons!}

Pero esto aplicará el estilo a todos los botones en lugar de a los botones que pertenecen comp-asolo a. Tienes que hacer algo como comp-a buttonpara que esto funcione correctamente.

El enfoque directo

Por tanto, propongo un mejor enfoque. En lugar de usar un mixin, dejamos que cada componente incluya el archivo de tema y use la definición de color directamente.

En este enfoque, el archivo de tema del componente se verá así:

// NOTE: just do this in your regular scss file.// No need to create separate theme file!// src/app/comp-a/[email protected] 'src/theme.scss';
$primary: map-get($my-theme, primary);button { background-color: mat-color($primary);}

Y eso es todo.

Let’s see how this works. First, theme related rules are put into the component SCSS file, so no extra component level theme file required. Second, the main theme.scss does not need to know component level themes (since it does not need to import them), so a simple theme definition is adequate. Third, the component SCSS file is used with @Component so it is encapsulated correctly, which means we can simply define rules for button.

Predefined Theme Keys

Probably you have noticed the next problem. What are the foreground, primary in above theme files ( map-get($my-theme, primary))? Are there any other keys I can use?

Well these “keys” refer to different colors defined in the theme. However I could not find any documents explaining these “keys”, so the only way I could find out is to read the source code. (Although it is said that good programmers should read the code, having to read the code is definitely not a good sign for a library.)

Open node_modules/@angular/material/_theming.scss and you will see the definitions for these keys. For future reference, I would like to summarize the keys here.

$theme |- primary |- accent |- warn |- foreground | |- base | |- divider | |- dividers | |- disabled | |- disabled-button | |- disabled-text | |- hint-text | |- secondary-text | |- icon | |- icons | |- text | |- slider-min | |- slider-off | `- slider-off-active |- background | |- status-bar | |- app-bar | |- background | |- hover | |- card | |- dialog | |- disabled-button | |- raised-button | |- focused-button | |- selected-button | |- selected-disabled-button | |- disabled-button-toggle | |- unselected-chip | `- disabled-list-option `- is-dark // bool, whether dark theme or not

For example, if you want to render a disabled text in your component, you may want to use the following code:

$foreground: map-get($my-theme, foreground);.disabled-text { color: mat-color($foreground, disabled-text);}

Okay these are some lessons I’ve learned from struggling with Angular Material. Hope this post is helpful if you are facing similar problems.