Directiva ngClass
La directiva ngClass es una herramienta fundamental en Angular que permite aplicar clases CSS de forma dinámica basándose en el estado del componente. A diferencia del property binding simple con [class]
, ngClass ofrece mayor flexibilidad para manejar múltiples clases con lógica condicional compleja.
Para utilizar ngClass en componentes standalone, debemos importar CommonModule en nuestro componente, ya que esta directiva forma parte de las directivas comunes de Angular.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-ejemplo',
standalone: true,
imports: [CommonModule],
template: `
<div [ngClass]="clasesDinamicas">
Contenido con clases dinámicas
</div>
`
})
export class EjemploComponent {
clasesDinamicas = 'destacado activo';
}
Sintaxis con string
La forma más sencilla de usar ngClass es pasando un string con los nombres de las clases separadas por espacios:
@Component({
selector: 'app-mensaje',
standalone: true,
imports: [CommonModule],
template: `
<div [ngClass]="tipoMensaje">
{{ contenido }}
</div>
`
})
export class MensajeComponent {
tipoMensaje = 'mensaje success';
contenido = 'Operación realizada correctamente';
cambiarTipo() {
this.tipoMensaje = 'mensaje error';
this.contenido = 'Error en la operación';
}
}
Sintaxis con array
Cuando necesitamos combinar clases de diferentes fuentes o aplicar lógica más compleja, podemos usar un array:
@Component({
selector: 'app-boton',
standalone: true,
imports: [CommonModule],
template: `
<button [ngClass]="clasesBoton" (click)="alternar()">
{{ texto }}
</button>
`
})
export class BotonComponent {
activo = false;
tipo = 'primary';
get clasesBoton() {
return [
'btn',
`btn-${this.tipo}`,
this.activo ? 'btn-activo' : 'btn-inactivo'
];
}
texto = 'Hacer clic';
alternar() {
this.activo = !this.activo;
this.texto = this.activo ? 'Activo' : 'Inactivo';
}
}
Sintaxis con objeto
La sintaxis más versátil utiliza un objeto donde las claves son los nombres de las clases y los valores son expresiones booleanas que determinan si la clase se aplica:
@Component({
selector: 'app-tarjeta',
standalone: true,
imports: [CommonModule],
template: `
<div [ngClass]="clasesTarjeta">
<h3>{{ titulo }}</h3>
<p>Estado: {{ estado }}</p>
<button (click)="cambiarEstado()">Cambiar estado</button>
</div>
`
})
export class TarjetaComponent {
titulo = 'Mi Tarjeta';
estado = 'pendiente';
importante = false;
get clasesTarjeta() {
return {
'tarjeta': true,
'tarjeta-pendiente': this.estado === 'pendiente',
'tarjeta-completada': this.estado === 'completada',
'tarjeta-error': this.estado === 'error',
'tarjeta-importante': this.importante,
'tarjeta-normal': !this.importante
};
}
cambiarEstado() {
const estados = ['pendiente', 'completada', 'error'];
const indiceActual = estados.indexOf(this.estado);
this.estado = estados[(indiceActual + 1) % estados.length];
this.importante = Math.random() > 0.5;
}
}
Comparación con property binding [class]
Aunque tanto ngClass como el property binding con [class] permiten aplicar clases dinámicamente, cada uno tiene sus casos de uso específicos:
Property binding [class] es ideal para:
// Para una sola clase condicional
<div [class.activo]="estaActivo">Contenido</div>
// Para clases simples basadas en string
<div [class]="'btn btn-' + tipoBoton">Botón</div>
ngClass es preferible cuando:
- Necesitas aplicar múltiples clases con diferentes condiciones
- La lógica condicional es compleja
- Quieres mantener toda la lógica de clases organizada en un lugar
// Ejemplo donde ngClass es más apropiado
@Component({
selector: 'app-estado-usuario',
standalone: true,
imports: [CommonModule],
template: `
<div [ngClass]="clasesUsuario">
<img [src]="usuario.avatar" alt="Avatar">
<span>{{ usuario.nombre }}</span>
<span class="estado">{{ usuario.estado }}</span>
</div>
`
})
export class EstadoUsuarioComponent {
usuario = {
nombre: 'Ana García',
estado: 'online',
avatar: '/assets/avatar.jpg',
esAdmin: true,
ultimaActividad: new Date()
};
get clasesUsuario() {
const ahora = new Date();
const tiempoInactivo = ahora.getTime() - this.usuario.ultimaActividad.getTime();
const minutosInactivo = tiempoInactivo / (1000 * 60);
return {
'usuario-card': true,
'usuario-online': this.usuario.estado === 'online',
'usuario-offline': this.usuario.estado === 'offline',
'usuario-ausente': this.usuario.estado === 'ausente',
'usuario-admin': this.usuario.esAdmin,
'usuario-inactivo': minutosInactivo > 5,
'usuario-vip': this.usuario.esAdmin && this.usuario.estado === 'online'
};
}
}
La ventaja principal de ngClass radica en su capacidad para manejar lógica compleja de manera limpia y mantenible, especialmente cuando múltiples condiciones determinan qué clases aplicar a un elemento.
Clases CSS dinámicas
Las clases CSS dinámicas representan uno de los aspectos más versátiles del desarrollo de interfaces modernas en Angular. Permiten crear experiencias de usuario interactivas y adaptables donde el aspecto visual responde automáticamente a los cambios de estado de la aplicación.
Patrones de diseño con clases dinámicas
Los temas dinámicos son uno de los casos más comunes donde las clases CSS dinámicas brillan. Permiten cambiar completamente la apariencia de la aplicación sin recargar la página:
@Component({
selector: 'app-tema-switcher',
standalone: true,
imports: [CommonModule],
template: `
<div [ngClass]="clasesAplicacion">
<header [ngClass]="clasesHeader">
<h1>Mi Aplicación</h1>
<button (click)="cambiarTema()" [ngClass]="clasesBotonTema">
{{ temaActual === 'claro' ? '🌙' : '☀️' }}
</button>
</header>
<main [ngClass]="clasesContenido">
<p>Este contenido cambia de tema dinámicamente</p>
</main>
</div>
`
})
export class TemaSwitcherComponent {
temaActual: 'claro' | 'oscuro' = 'claro';
get clasesAplicacion() {
return {
'app-container': true,
'tema-claro': this.temaActual === 'claro',
'tema-oscuro': this.temaActual === 'oscuro'
};
}
get clasesHeader() {
return {
'header': true,
'header-claro': this.temaActual === 'claro',
'header-oscuro': this.temaActual === 'oscuro'
};
}
get clasesBotonTema() {
return {
'btn-tema': true,
'btn-claro': this.temaActual === 'claro',
'btn-oscuro': this.temaActual === 'oscuro'
};
}
get clasesContenido() {
return {
'contenido': true,
'contenido-claro': this.temaActual === 'claro',
'contenido-oscuro': this.temaActual === 'oscuro'
};
}
cambiarTema() {
this.temaActual = this.temaActual === 'claro' ? 'oscuro' : 'claro';
}
}
Estados de carga y feedback visual
Las clases dinámicas son esenciales para proporcionar feedback visual durante operaciones asíncronas:
@Component({
selector: 'app-formulario-envio',
standalone: true,
imports: [CommonModule],
template: `
<form [ngClass]="clasesFormulario" (ngSubmit)="enviarDatos()">
<div [ngClass]="clasesInput">
<input
type="email"
[(ngModel)]="email"
placeholder="Tu email"
[disabled]="enviando"
>
</div>
<button
type="submit"
[ngClass]="clasesBoton"
[disabled]="!email || enviando"
>
<span [ngClass]="clasesTextoBoton">
@if (enviando) {
Enviando...
} @else if (enviado) {
✓ Enviado
} @else {
Enviar
}
</span>
</button>
@if (error) {
<div [ngClass]="clasesError">
Error: {{ mensajeError }}
</div>
}
</form>
`
})
export class FormularioEnvioComponent {
email = '';
enviando = false;
enviado = false;
error = false;
mensajeError = '';
get clasesFormulario() {
return {
'formulario': true,
'formulario-enviando': this.enviando,
'formulario-completado': this.enviado,
'formulario-error': this.error
};
}
get clasesInput() {
return {
'input-container': true,
'input-disabled': this.enviando,
'input-success': this.enviado,
'input-error': this.error
};
}
get clasesBoton() {
return {
'btn': true,
'btn-primary': !this.enviando && !this.enviado,
'btn-loading': this.enviando,
'btn-success': this.enviado,
'btn-disabled': !this.email || this.enviando
};
}
get clasesTextoBoton() {
return {
'btn-texto': true,
'btn-texto-loading': this.enviando,
'btn-texto-success': this.enviado
};
}
get clasesError() {
return {
'mensaje-error': true,
'mensaje-error-visible': this.error,
'mensaje-error-shake': this.error
};
}
async enviarDatos() {
this.enviando = true;
this.error = false;
this.enviado = false;
try {
// Simular petición HTTP
await new Promise(resolve => setTimeout(resolve, 2000));
if (Math.random() > 0.3) { // 70% de éxito
this.enviado = true;
this.email = '';
} else {
throw new Error('Error de conexión');
}
} catch (err) {
this.error = true;
this.mensajeError = 'No se pudo enviar el formulario';
} finally {
this.enviando = false;
}
}
}
Sistemas de notificaciones con clases dinámicas
Un sistema de notificaciones demuestra cómo las clases dinámicas pueden crear experiencias de usuario sofisticadas:
interface Notificacion {
id: number;
tipo: 'info' | 'success' | 'warning' | 'error';
mensaje: string;
mostrar: boolean;
eliminando: boolean;
}
@Component({
selector: 'app-notificaciones',
standalone: true,
imports: [CommonModule],
template: `
<div class="notificaciones-container">
@for (notificacion of notificaciones; track notificacion.id) {
<div [ngClass]="clasesNotificacion(notificacion)">
<div class="notificacion-icono">
{{ obtenerIcono(notificacion.tipo) }}
</div>
<div class="notificacion-contenido">
{{ notificacion.mensaje }}
</div>
<button
class="notificacion-cerrar"
(click)="cerrarNotificacion(notificacion.id)"
>
×
</button>
</div>
}
</div>
<div class="controles">
<button (click)="agregarNotificacion('info')">Info</button>
<button (click)="agregarNotificacion('success')">Éxito</button>
<button (click)="agregarNotificacion('warning')">Advertencia</button>
<button (click)="agregarNotificacion('error')">Error</button>
</div>
`
})
export class NotificacionesComponent {
notificaciones: Notificacion[] = [];
private contadorId = 0;
clasesNotificacion(notificacion: Notificacion) {
return {
'notificacion': true,
'notificacion-info': notificacion.tipo === 'info',
'notificacion-success': notificacion.tipo === 'success',
'notificacion-warning': notificacion.tipo === 'warning',
'notificacion-error': notificacion.tipo === 'error',
'notificacion-visible': notificacion.mostrar,
'notificacion-oculta': !notificacion.mostrar,
'notificacion-eliminando': notificacion.eliminando,
'notificacion-slide-in': notificacion.mostrar && !notificacion.eliminando,
'notificacion-slide-out': notificacion.eliminando
};
}
agregarNotificacion(tipo: Notificacion['tipo']) {
const mensajes = {
info: 'Esta es una notificación informativa',
success: 'Operación completada correctamente',
warning: 'Ten cuidado con esta acción',
error: 'Ha ocurrido un error inesperado'
};
const nuevaNotificacion: Notificacion = {
id: ++this.contadorId,
tipo,
mensaje: mensajes[tipo],
mostrar: false,
eliminando: false
};
this.notificaciones.push(nuevaNotificacion);
// Mostrar con animación
setTimeout(() => {
nuevaNotificacion.mostrar = true;
}, 10);
// Auto-eliminar después de 5 segundos
setTimeout(() => {
this.cerrarNotificacion(nuevaNotificacion.id);
}, 5000);
}
cerrarNotificacion(id: number) {
const notificacion = this.notificaciones.find(n => n.id === id);
if (notificacion && !notificacion.eliminando) {
notificacion.eliminando = true;
notificacion.mostrar = false;
// Eliminar del array después de la animación
setTimeout(() => {
this.notificaciones = this.notificaciones.filter(n => n.id !== id);
}, 300);
}
}
obtenerIcono(tipo: string): string {
const iconos = {
info: 'ℹ️',
success: '✅',
warning: '⚠️',
error: '❌'
};
return iconos[tipo as keyof typeof iconos] || 'ℹ️';
}
}
Optimización de rendimiento
Para mejorar el rendimiento con clases dinámicas, es importante considerar la frecuencia de evaluación de las expresiones:
@Component({
selector: 'app-lista-optimizada',
standalone: true,
imports: [CommonModule],
template: `
@for (item of items; track item.id) {
<div [ngClass]="obtenerClasesItem(item)">
{{ item.nombre }} - {{ item.estado }}
</div>
}
`
})
export class ListaOptimizadaComponent {
items = [
{ id: 1, nombre: 'Tarea 1', estado: 'pendiente', prioridad: 'alta' },
{ id: 2, nombre: 'Tarea 2', estado: 'completada', prioridad: 'media' },
{ id: 3, nombre: 'Tarea 3', estado: 'en-progreso', prioridad: 'baja' }
];
// Cache para evitar recalcular clases innecesariamente
private clasesCache = new Map<number, any>();
obtenerClasesItem(item: any) {
// Usar cache si el item no ha cambiado
const cacheKey = `${item.id}-${item.estado}-${item.prioridad}`;
if (this.clasesCache.has(cacheKey)) {
return this.clasesCache.get(cacheKey);
}
const clases = {
'item': true,
'item-pendiente': item.estado === 'pendiente',
'item-completada': item.estado === 'completada',
'item-en-progreso': item.estado === 'en-progreso',
'item-prioridad-alta': item.prioridad === 'alta',
'item-prioridad-media': item.prioridad === 'media',
'item-prioridad-baja': item.prioridad === 'baja'
};
this.clasesCache.set(cacheKey, clases);
return clases;
}
}
Las clases CSS dinámicas transforman interfaces estáticas en experiencias interactivas y responsivas, permitiendo que los elementos visuales reaccionen inteligentemente a los cambios de estado de la aplicación. Esta capacidad es fundamental para crear aplicaciones modernas que ofrezcan feedback visual inmediato y una experiencia de usuario fluida.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Angular
Documentación oficial de Angular
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Angular
Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender la función y ventajas de la directiva ngClass en Angular.
- Aprender las diferentes sintaxis para aplicar clases dinámicas: string, array y objeto.
- Diferenciar cuándo usar ngClass frente al property binding con [class].
- Implementar patrones comunes de clases dinámicas para temas, estados y notificaciones.
- Optimizar el rendimiento al manejar clases dinámicas en listas y componentes complejos.