En esta página
Directivas y pipes personalizados
Directivas en Angular
Las directivas extienden el comportamiento de elementos HTML. Hay dos tipos principales:
- Directivas de atributo — Modifican la apariencia o comportamiento de un elemento
- Directivas estructurales — Modifican la estructura del DOM (agregar/quitar elementos)
Directivas de atributo
Crean comportamiento reutilizable que se aplica a elementos existentes:
import { Directive, ElementRef, inject, input } from '@angular/core';
@Directive({
selector: '[appTooltip]',
host: {
'(mouseenter)': 'mostrar()',
'(mouseleave)': 'ocultar()',
'[attr.aria-label]': 'appTooltip()',
},
})
export class TooltipDirective {
readonly appTooltip = input.required<string>();
mostrar(): void { /* lógica del tooltip */ }
ocultar(): void { /* ocultar tooltip */ }
}Uso en el template:
<button [appTooltip]="'Guardar cambios'">Guardar</button>Directivas estructurales
Modifican la estructura del DOM. Usan TemplateRef y ViewContainerRef:
import { Directive, TemplateRef, ViewContainerRef, inject, input, effect } from '@angular/core';
@Directive({ selector: '[appRepetir]' })
export class RepetirDirective {
private readonly template = inject(TemplateRef);
private readonly container = inject(ViewContainerRef);
readonly appRepetir = input.required<number>();
constructor() {
effect(() => {
this.container.clear();
for (let i = 0; i < this.appRepetir(); i++) {
this.container.createEmbeddedView(this.template);
}
});
}
}Pipes: transformar datos en templates
Los pipes transforman datos para la presentación. Se usan con el operador | en templates.
Pipes integrados de Angular
| Pipe | Uso | Ejemplo |
|---|---|---|
date |
Formatear fechas | {{ fecha | date:'short' }} |
currency |
Formatear moneda | {{ precio | currency:'USD' }} |
uppercase |
Mayusculas | {{ texto | uppercase }} |
lowercase |
Minusculas | {{ texto | lowercase }} |
titlecase |
Capitalizar | {{ texto | titlecase }} |
number |
Formatear números | {{ valor | number:'1.2-2' }} |
percent |
Porcentaje | {{ ratio | percent }} |
json |
Serializar a JSON | {{ objeto | json }} |
slice |
Extraer porcion | {{ lista | slice:0:5 }} |
async |
Resolver Observable/Promise | {{ obs$ | async }} |
Crear un pipe personalizado
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'iniciales' })
export class InicialesPipe implements PipeTransform {
transform(nombreCompleto: string): string {
if (!nombreCompleto) return '';
return nombreCompleto
.split(' ')
.map(palabra => palabra[0])
.join('')
.toUpperCase();
}
}Uso:
<span class="avatar">{{ usuario.nombre | iniciales }}</span>
<!-- "Carlos Morales" -> "CM" -->Pipes con parametros
@Pipe({ name: 'filtrar' })
export class FiltrarPipe implements PipeTransform {
transform<T>(lista: T[], campo: keyof T, valor: T[keyof T]): T[] {
return lista.filter(item => item[campo] === valor);
}
}@for (activo of usuarios | filtrar:'estado':'activo'; track activo.id) {
<span>{{ activo.nombre }}</span>
}Importar directivas y pipes
En Angular 21, importa directivas y pipes directamente en el componente:
@Component({
imports: [HighlightDirective, TiempoRelativoPipe],
template: `
<p appHighlight>{{ fecha | tiempoRelativo }}</p>
`,
})Práctica
- Crea una directiva de atributo: Implementa una directiva
appTooltipque muestre un texto flotante al hacer hover sobre un elemento. Usa la propiedadhostdel decorador en lugar de@HostListener. - Crea un pipe personalizado: Implementa un pipe
tiempoTranscurridoque reciba una fecha y devuelva un string como "hace 5 min", "hace 2 h" o "hace 3 dias". - Crea un pipe con parametros: Implementa un pipe
resaltarque reciba un texto y un termino de busqueda, y devuelva el texto con el termino envuelto en una etiqueta<mark>.
En la siguiente leccion aprenderemos sobre Server-Side Rendering y pre-rendering para mejorar el SEO y rendimiento.
Pipes puros vs impuros
Los pipes puros (pure: true, por defecto) solo se recalculan cuando la referencia del input cambia. Los impuros (pure: false) se recalculan en cada ciclo de detección de cambios. Prefiere pipes puros.
host sobre HostListener
En Angular 21, usa la propiedad host del decorador @Directive en lugar de @HostListener y @HostBinding. Es más declarativo y funciona mejor con el compilador AOT.
import {
Directive, Pipe, PipeTransform,
ElementRef, TemplateRef, ViewContainerRef,
inject, input, effect,
} from '@angular/core';
import { AuthService } from './auth.service';
// --- Directiva de atributo: highlight ---
@Directive({
selector: '[appHighlight]',
host: {
'(mouseenter)': 'activar()',
'(mouseleave)': 'desactivar()',
'[style.transition]': '"background-color 0.3s ease"',
},
})
export class HighlightDirective {
private readonly el = inject(ElementRef);
readonly appHighlight = input('var(--brand-amber)');
activar(): void {
this.el.nativeElement.style.backgroundColor = this.appHighlight();
}
desactivar(): void {
this.el.nativeElement.style.backgroundColor = '';
}
}
// --- Directiva estructural: permisos ---
@Directive({
selector: '[appSiRol]',
})
export class SiRolDirective {
private readonly templateRef = inject(TemplateRef);
private readonly viewContainer = inject(ViewContainerRef);
readonly appSiRol = input.required<string>();
constructor() {
const auth = inject(AuthService);
effect(() => {
const rolRequerido = this.appSiRol();
const usuario = auth.usuario();
if (usuario?.rol === rolRequerido) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
});
}
}
// --- Pipe: tiempo relativo ---
@Pipe({
name: 'tiempoRelativo',
pure: true,
})
export class TiempoRelativoPipe implements PipeTransform {
transform(fecha: string | Date): string {
const ahora = Date.now();
const pasado = new Date(fecha).getTime();
const diff = ahora - pasado;
const minutos = Math.floor(diff / 60000);
const horas = Math.floor(diff / 3600000);
const dias = Math.floor(diff / 86400000);
if (minutos < 1) return 'Justo ahora';
if (minutos < 60) return `Hace ${minutos} min`;
if (horas < 24) return `Hace ${horas} h`;
if (dias < 30) return `Hace ${dias} dias`;
return new Date(fecha).toLocaleDateString('es');
}
}
// --- Pipe: truncar texto ---
@Pipe({ name: 'truncar' })
export class TruncarPipe implements PipeTransform {
transform(texto: string, longitud = 100): string {
if (!texto || texto.length <= longitud) return texto;
return texto.substring(0, longitud).trimEnd() + '...';
}
}
Inicia sesión para guardar tu progreso