Cuando trabajas con componentes, a menudo necesitas añadir estilos al propio componente (lo que se conoce como el host).

Añadir estilos es fácil con el selector de pseudo-clase CSS :host, pero ¿qué pasa cuando quieres aplicarle una clase directamente?

Escenario: Quiero asignar una clase al host del componente

Imagina que tienes un componente blog-card y un componente book-card. No son iguales. Sus templates son diferentes, pero hay una componente genérica en su diseño como ves en la imagen, ambos componentes son tarjetas.

blog-car VS book-card

Imagina ahora que has creado una clase CSS .card, en la que defines unos estilos básicos a aplicar sobre ambos componentes. Esta clase la puedes tener definida a nivel global.

.card{
    display: flex;
    flex-direction: column;
    box-shadow: 0 0 15px rgba(0, 0, 0, 0.09);
    background-color: white;    

    &:hover{
        box-shadow: 0 10px 35px rgba(0, 0, 0, 0.19);
    }

    .content{
        margin-left: 24px;
        margin-right: 24px;
        margin-bottom: 70px;
    }

    .image{
        min-height: 319px;
        background-position: center;
        background-size: cover;
        flex-shrink: 0;
    }
}

Pista: Lo que NO quieres

Obviamente, tú quieres que cada uno de estos componentes tenga la clase .card de inicio, sin necesidad de añadirla externamente.

Es decir, lo que NO quieres, es añadir la clase .card cada vez que los usas, como se haría en el siguiente ejemplo:

<!-- what you DON'T WANT TO DO -->

<!-- ... some view ...-->
  <section id="books">
    <h1>Books</h1>
    <div>
        <app-book-card class="card" [item]="books['0']">
        </app-book-card>
        <app-book-card class="card" [item]="books['1']">
        </app-book-card>        
    </div> 
  </section>

  <section id="posts">
    <h1>Blog posts</h1>
    <div>
        <app-blog-card class="card" [item]="blog['0']">
        </app-blog-card>
        <app-blog-card class="card" [item]="blog['1']">
        </app-blog-card>        
    </div> 
  </section>

Aproximación old school: Usar un wrapper

Podrías encapsular todo el contenido de cada componente en un DIV de clase .card, es decir:

blog-card template

<!-- blog-card.component.html -->
<div class="card">
    <div class="image" [style.background-image]="'url(' + item.image + ')'"></div>
    <div class="content">
        <div class="date">{{item.date}}</div>
        <h1>{{item.title}}</h1>
    </div>
</div>   

book-card template

<!-- book-card.component.html -->
<div class="card">
    <div class="image" [style.background-image]="'url(' + item.image + ')'"></div>
    <div class="content">
        <header>
            <div class="type">{{item.type}}</div>
            <div class="language">Language: <span>{{item.lang}}</span></div>
        </header>
        <h2>{{item.title}}</h2>
        <p>{{item.description}}</p>
        <footer>
            <button *ngIf="!item.link">free</button>
            <span *ngIf="item.reviews.length" class="reviews_btn">See reviews</span>
        </footer>
    </div>
</div>   

Esta es la aproximación obvia, claro… pero ¿y si por cuestiones de eficiencia prefieres saltarte ese contenedor intermedio y usar directamente el componente como contenedor?

¿Como le aplicas la clase .card al host de cada componente sin añadir un wrapper?

Aproximación Angular: Definir la clase en los metadatos

La aproximación the Angular way, sería utilizar la propiedad host de los metadatos del componente. Es una propiedad poco conocida, pero ahí está, para hacerte la vida más fácil.

Simplemente tienes que actualizar los datos que le pasas al decorador de tu componente, para incluir esta propiedad host:

blog-card.component.ts

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-blog-card',
  host: {'class': 'card'},
  templateUrl: './blog-card.component.html',
  styleUrls: ['./blog-card.component.scss']
})
export class BlogCardComponent implements OnInit {
    //...regular stuff...
}

De este modo, podría pasar del wrapper anterior y simplificar el template de este componente a:

blog-card template

<!-- blog-card.component.html -->
<div class="image" [style.background-image]="'url(' + item.image + ')'"></div>
<div class="content">
    <div class="date">{{item.date}}</div>
    <h1>{{item.title}}</h1>
</div>

Bonus track: clases dinámicas en el host

La aproximación anterior también la puedes usar para aplicar al host clases dinámicas en función de algún parámetro.

Imagina que quieres destacar los posts de hoy aplicando una clase adicional .new cuando se cumple esta condición. Solo hay que definir el binding de la nueva clase a la propiedad que valida la condición. Así:

blog-card.component.ts

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-blog-card',
  host: {
    'class': 'card', 
    '[class.new]': 'isNew'
  },
  templateUrl: './blog-card.component.html',
  styleUrls: ['./blog-card.component.scss']
})
export class BlogCardComponent implements OnInit {

  isNew:boolean = false;
  @Input() item:any;

  constructor() { }

  ngOnInit() {
    let today = new Date().toISOString().slice(0, 10);
    if(this.item.date == today)
      this.isNew = true;
  }
}

Fíjate como he tenido que definir la propiedad isNew, y como la actualizo en el método ngOnInit().

De este modo, si hay un <app-blog-card> que cumple la condición de fecha especificada, se le aplicará además la clase new, que puedes tener definida a nivel global.

Es más, podrías modificar también tu componente para adecuar sus estilos (a nivel particular) cuando se le aplica una clase determinada (como este .new). Esto lo harías desde la hoja de estilos del componente, pasando la clase al selector :host :

blog-card.component.scss

:host(.new){
    h1{
        color: limegreen !important;
    }
}

Mis reflexiones

El decorador Component es una herramienta muy potente, que va más allá de definir el selector, template y hoja de estilos asociados a un componente. Espero haber arrojado un poco de luz sobre una propiedad poco conocida de sus metadatos, que te ayude a simplificar el DOM la próxima vez que vayas a usar un wrapper solo para aplicar clases al propio componente.

Si te ha gustado este artículo, compártelo 😉