🇺🇸 EN
Frontend
·6 min read·↗·🇺🇸 EN

Temas en componentes de Angular: Guía para principiantes

Portada principal del artículo: Temas en componentes de Angular: Guía para principiantes

Cuando construyo componentes, siempre busco que sean flexibles. Solemos utilizarlos en muchos lugares y contextos diferentes donde, en ocasiones, necesitamos cambiar la estructura visual o los colores.

Por ejemplo, imagina que un cliente nos pide una lista de contactos: necesita mostrarse como una tarjeta con foto, nombre y detalles, o en modo lista con bordes marcados, pero en tonos gris y blanco por defecto y sin la foto.

Además, queremos tener la posibilidad de añadir nuevas estructuras y esquemas de colores en el futuro y aplicarlos fácilmente. En resumen, algo como esto:

Alt Text
Alt Text

Empecemos

Para lograrlo, combino tres herramientas fundamentales: la pseudoclase :host(), Angular y las CSS Custom Properties (variables de CSS).

Alt Text
Alt Text

La estructura inicial

Primero, creo el componente app-contact y el marcado HTML para los contactos.

ng g c contact

Siguiendo la metodología BEM, asigno una clase a cada elemento para mantener la especificidad baja.

<div class="contact">
  <h1 class="contact__firstname">{{contact.first_name}}</h1>
  <p class="contact__lastname">{{contact.last_name}}</p>
  <img  class="contact__avatar" [src]="contact.avatar"/>
</div>

Ahora edito el archivo Sass del componente con los estilos por defecto.

.contact {
    background: grey;
    font-family: monospace;
    border: 1px solid black;
    border-radius: 5px;
    margin: 10px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px;
    &__firstname {
        font-size: 1.5em;
        color: whitesmoke;
    }
    &__lastname {
        font-size: 1.5em;
        color: whitesmoke;
    }
    &__avatar {
        display: none;
        border: 1px solid black;
        background-color: lightblue;
    }
}

Con esto, ya tenemos la estructura por defecto lista y funcionando:

Alt Text
Alt Text

La maquetación inicial funciona, pero cometimos un par de errores: los colores están fijos en el código (hardcoded), y la estructura y los colores se encuentran mezclados en el mismo archivo.

Temas y colores

Para solucionarlo, separo cada caso en archivos independientes para estructura y colores. Creamos una carpeta themes con los siguientes archivos:

  • winter.scss
  • winter-colors.scss
  • summer.scss
  • summer-colors.scss

Cómo usar :host y CSS Custom Properties

La pseudoclase :host nos ayuda a aplicar estilos únicamente cuando el componente coincide o tiene una clase CSS específica.

Puedes leer más sobre :host() en la documentación de Angular.

Por su parte, las CSS Custom Properties nos permiten almacenar valores reutilizables directamente en CSS, de forma similar a las variables de Sass.

Puedes consultar más detalles sobre CSS Custom Properties en MDN.

Con esto en mente, defino los colores de invierno usando variables CSS dentro de winter-colors.scss:

:host(.contact-winter-colors) {
    --background-color: #424b68;
    --primary-color: rgb(220, 59, 226);
    --secondary-color: rgb(80, 245, 65);
    --avatar-background: rgb(48, 109, 78);  
}

Luego, creo la estructura de invierno (winter.scss) usando las variables CSS para asignar los colores y adaptar los cambios de diseño en los elementos:

:host(.contact-winter) {
    .contact {
        background: var(--background-color);
        font-family: monospace;
        border: 1px solid black;
        border-radius: 5px;
        width: -moz-fit-content;
        min-width: 150px;
        flex-direction: column;
        padding: 10px;
        text-align: center;
        display: table-cell;
        
    &__firstname {
        font-size: 1.5em;
        color: var(--primary-color);
    }
    &__lastname {
        font-size: 1.5em;
        color: var(--secondary-color);
    }
    &__avatar {
        display: block;
        border: 1px solid black;
        border-radius: 50%;
        background-color: var(--avatar-background);
    }
}
}

Repite exactamente los mismos pasos para summer.scss y summer-colors.scss.

Importar temas y colores

Dentro de contact.component.scss, importo los archivos de estructura y temas de color:

/* Estructuras */
@import './themes/summer';
@import './themes/winter';
 
/* Temas de color */
@import './themes/summer-colors';
@import './themes/winter-colors';

Puedes consultar más información sobre cómo importar archivos en Sass.

El archivo Sass principal del componente contiene los estilos por defecto y las referencias a las estructuras y colores.

Ahora queremos sustituir esos colores base por los temas de verano o invierno. Para lograrlo, nos apoyamos en los valores de reserva (fallback) de las CSS Custom Properties.

Puedes leer más sobre valores de reserva (fallback).

De esta manera, si --background-color tiene un valor definido, lo utiliza; si no, toma grey como valor por defecto:

background: var(--background-color, grey);

Así, el estilo base queda preparado para recibir los valores dinámicos de las variables CSS o mantener el valor por defecto.

Dinamismo con ngClass y :host

Para alternar los colores y la estructura de forma dinámica, uso la directiva ngClass de Angular en la etiqueta del componente:

<app-contact [ngClass]="theme" *ngFor="let contact of contacts" [contact]="contact">      
</app-contact>

Para controlarlo en tiempo de ejecución, creo una propiedad theme en el componente y la actualizo con métodos como changeLayoutColor, addColor y un reinicio:

  <div class="actions">
    <button (click)="changeLayoutColor()">change</button>
    <button (click)="addColor('contact-winter-colors')">Winter</button>
    <button (click)="addColor('contact-summer-colors')">Summer</button>
    <button (click)="theme = ''">reset</button>
    <p>
      current theme: {{theme}}
    </p>
  </div>
 theme = '';
  changeLayoutColor() {
    this.theme = this.theme === 'contact-winter' ? 'contact-summer' : 'contact-winter';
  }
  addColor(color:string) {
    this.theme += ` ${color}`
  }

Al cambiar la clase en el host, la pseudoclase :host detecta la coincidencia y aplica los estilos correspondientes de inmediato.

Relacionar temas entre componentes

Cuando creamos un componente y lo reutilizamos en distintas partes de la aplicación, muchas veces su estilo visual necesita adaptarse al contenedor o al contexto donde vive.

Para estos casos, la pseudoclase :host-context permite vincular estilos con elementos ancestros o contenedores.

Puedes leer más en la documentación oficial sobre :host-context.

Por ejemplo, imagina que la aplicación principal tiene un componente de botón dentro de un componente de producto. El botón necesita adaptar sus estilos según el lugar donde se encuentre.

Si el componente producto o la aplicación principal cambian sus colores, el botón debe reaccionar en consecuencia. En resumen, algo como esto:

Alt Text
Alt Text

Veamos cómo implementar este comportamiento con el selector :host-context.

Cómo funciona :host-context()

El pseudoselector :host-context nos permite crear una relación directa con el entorno donde vive el componente. Por ejemplo, vincular el componente producto con el componente raíz my-app:

/* product.component.css */
:host-context(my-app.dark) .content {
  background-color: black;
  color: white;
}

Cuando el componente my-app recibe la clase .dark, el componente producto aplica estos estilos de forma automática porque el selector CSS coincide en la jerarquía.

A partir de esta idea, podemos definir múltiples relaciones contextuales.

Múltiples relaciones contextuales

Ya sabemos cómo hacer coincidir un selector con el fondo. Ahora definamos varias reglas para nuestro botón:

  • Fondo en blanco humo (whitesmoke) cuando app-product tenga la clase .day.
  • Fondo azul cuando app-product tenga la clase .dark.
  • Fondo rosa cuando my-app tenga la clase .dark.

Edito el archivo button.component.css y agrego las siguientes reglas para modificar la clase .btn:

/* Archivo button.css */
/* Relación con app-product con la clase day */
:host-context(app-product.day) .btn {
  background-color: whitesmoke;
}
 
/* Relación con app-product con la clase dark */
:host-context(app-product.dark) .btn {
  background-color: black;
  color: whitesmoke;
}
 
/* Relación con my-app con la clase dark */
:host-context(my-app.dark) .btn {
  background-color: pink;
  color: white;
}

Con esto, el componente botón responde tanto a su contenedor directo como a la aplicación principal.

Si quieres probar el código en vivo y observar cómo reaccionan los componentes hijos según el contexto, preparé una demo interactiva en StackBlitz:

https://stackblitz.com/edit/host-context-angular-demo

Conclusión

¡Eso es todo! Espero que esta guía te sirva para organizar temas visuales, definir colores dinámicos y conectar estilos entre componentes aprovechando la pseudoclase :host-context y las variables CSS.

Si te gustó el artículo y te resultó útil, compártelo con tu equipo o en redes sociales.

También puedes explorar la demo desplegada aquí: https://theme-angular-components.surge.sh/

Foto de laura adai en Unsplash

Parte de la serie Frontend

Modern frontend development — Angular, React, TypeScript, accessibility, and performance.

Ver serie completa

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.