Pipes personalizados
Los pipes personalizados nos permiten crear transformaciones específicas para nuestras aplicaciones que van más allá de los pipes integrados que ofrece Angular. Cuando necesitamos formatear datos de una manera particular o aplicar lógica de transformación que no está cubierta por los pipes estándar, crear nuestros propios pipes es la solución ideal.
Un pipe personalizado es simplemente una clase TypeScript que implementa la interfaz PipeTransform
y está decorada con el decorador @Pipe
. Esta clase debe exportar un método llamado transform()
que recibe el valor a transformar y opcionalmente parámetros adicionales.
Estructura básica de un pipe personalizado
Para crear un pipe personalizado, necesitamos seguir esta estructura fundamental:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'nombreDelPipe',
standalone: true
})
export class MiPipePersonalizadoPipe implements PipeTransform {
transform(value: any, ...args: any[]): any {
// Lógica de transformación aquí
return valorTransformado;
}
}
El decorador @Pipe
requiere al menos la propiedad name, que será el nombre que usaremos en nuestros templates. Al establecer standalone: true
, nuestro pipe puede ser importado directamente en componentes standalone sin necesidad de módulos.
Ejemplo práctico: Pipe para truncar texto
Vamos a crear un pipe que trunca texto cuando supera una longitud determinada, algo muy útil para mostrar resúmenes o previsualizaciones:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate',
standalone: true
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 50, suffix: string = '...'): string {
if (!value) return '';
if (value.length <= limit) {
return value;
}
return value.substring(0, limit) + suffix;
}
}
Este pipe acepta tres parámetros: el texto a truncar, el límite de caracteres (con valor por defecto de 50), y el sufijo a añadir (por defecto '...').
Para usarlo en un componente standalone, debemos importarlo en el array imports
:
import { Component } from '@angular/core';
import { TruncatePipe } from './truncate.pipe';
@Component({
selector: 'app-articulo',
imports: [TruncatePipe],
template: `
<h3>{{ titulo }}</h3>
<p>{{ contenido | truncate:100:'...' }}</p>
<p>{{ resumen | truncate }}</p>
`
})
export class ArticuloComponent {
titulo = 'Mi artículo';
contenido = 'Este es un texto muy largo que necesita ser truncado para mostrar solo una preview del contenido completo...';
resumen = 'Texto corto';
}
Pipe para formateo de números de teléfono
Otro ejemplo práctico es crear un pipe que formate números de teléfono:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'telefono',
standalone: true
})
export class TelefonoPipe implements PipeTransform {
transform(value: string): string {
if (!value) return '';
// Eliminar todos los caracteres no numéricos
const numeroLimpio = value.replace(/\D/g, '');
// Formatear como: +34 123 456 789
if (numeroLimpio.length === 11 && numeroLimpio.startsWith('34')) {
return `+${numeroLimpio.substring(0, 2)} ${numeroLimpio.substring(2, 5)} ${numeroLimpio.substring(5, 8)} ${numeroLimpio.substring(8)}`;
}
// Formatear como: 123 456 789 (para números de 9 dígitos)
if (numeroLimpio.length === 9) {
return `${numeroLimpio.substring(0, 3)} ${numeroLimpio.substring(3, 6)} ${numeroLimpio.substring(6)}`;
}
return value; // Devolver original si no coincide con el formato esperado
}
}
Pipe para resaltar texto
Un pipe útil para resaltar coincidencias en texto, ideal para funciones de búsqueda:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'resaltar',
standalone: true
})
export class ResaltarPipe implements PipeTransform {
transform(texto: string, termino: string): string {
if (!texto || !termino) return texto;
const regex = new RegExp(`(${termino})`, 'gi');
return texto.replace(regex, '<mark>$1</mark>');
}
}
Para usar este pipe necesitamos también importar la directiva de Angular que permite HTML dinámico:
import { Component } from '@angular/core';
import { ResaltarPipe } from './resaltar.pipe';
@Component({
selector: 'app-busqueda',
imports: [ResaltarPipe],
template: `
<input [(ngModel)]="terminoBusqueda" placeholder="Buscar...">
<div [innerHTML]="texto | resaltar:terminoBusqueda"></div>
`
})
export class BusquedaComponent {
texto = 'Angular es un framework increíble para crear aplicaciones web';
terminoBusqueda = '';
}
Consideraciones importantes
Al crear pipes personalizados, es importante tener en cuenta que deben ser funciones puras siempre que sea posible. Esto significa que para la misma entrada, siempre deben devolver la misma salida, sin efectos secundarios. Esta característica permite a Angular optimizar su rendimiento al cachear los resultados.
Los pipes personalizados son especialmente útiles para transformaciones específicas del dominio de nuestra aplicación, como formateo de códigos de producto, conversión de unidades de medida, o cualquier lógica de presentación que se repita a lo largo de la aplicación.
Transform y PipeTransform
La interfaz PipeTransform es el corazón de cualquier pipe personalizado en Angular. Esta interfaz define el contrato que debe cumplir toda clase que funcione como pipe, estableciendo la firma del método transform()
que será invocado automáticamente por Angular cuando el pipe se ejecute en el template.
La interfaz PipeTransform
La interfaz PipeTransform
es extremadamente simple pero fundamental:
interface PipeTransform {
transform(value: any, ...args: any[]): any;
}
Esta interfaz obliga a implementar el método transform(), que recibe como primer parámetro el valor a transformar y como parámetros adicionales cualquier argumento que se pase al pipe desde el template.
Anatomía del método transform()
El método transform()
es donde reside toda la lógica de transformación de nuestro pipe. Veamos sus características principales:
transform(value: any, ...args: any[]): any {
// value: el valor que llega desde el template
// args: array con los argumentos adicionales del pipe
// return: el valor transformado que se mostrará
}
Ejemplo detallado de cómo funciona internamente:
@Pipe({
name: 'formato',
standalone: true
})
export class FormatoPipe implements PipeTransform {
transform(value: number, decimales: number = 2, simbolo: string = '€'): string {
console.log('Valor recibido:', value);
console.log('Decimales:', decimales);
console.log('Símbolo:', simbolo);
if (value == null || isNaN(value)) {
return '';
}
const valorFormateado = value.toFixed(decimales);
return `${valorFormateado} ${simbolo}`;
}
}
Cuando usamos este pipe en el template como {{ precio | formato:3:'$' }}
, Angular internamente llama a transform(precio, 3, '$')
.
Pipes puros vs impuros
Por defecto, todos los pipes en Angular son puros (pure: true
). Un pipe puro solo se ejecuta cuando Angular detecta un cambio en el valor de entrada o en los parámetros del pipe. Esta característica es fundamental para el rendimiento:
@Pipe({
name: 'pipeEjemplo',
pure: true, // Valor por defecto
standalone: true
})
export class PipeEjemploPipe implements PipeTransform {
transform(value: string): string {
console.log('Pipe ejecutado'); // Solo se verá cuando cambie value
return value.toUpperCase();
}
}
En cambio, un pipe impuro se ejecuta en cada ciclo de detección de cambios, independientemente de si los valores de entrada han cambiado:
@Pipe({
name: 'fechaActual',
pure: false, // Pipe impuro
standalone: true
})
export class FechaActualPipe implements PipeTransform {
transform(formato: string = 'short'): string {
console.log('Pipe impuro ejecutado'); // Se ejecuta constantemente
return new Date().toLocaleDateString('es-ES');
}
}
Cuándo usar pipes impuros
Los pipes impuros deben usarse con extrema precaución debido a su impacto en el rendimiento. Son útiles únicamente en casos muy específicos:
@Pipe({
name: 'filtrarArray',
pure: false,
standalone: true
})
export class FiltrarArrayPipe implements PipeTransform {
transform(items: any[], propiedad: string, valor: any): any[] {
if (!items || !propiedad) return items;
return items.filter(item => item[propiedad] === valor);
}
}
Este ejemplo requiere ser impuro porque Angular no detecta cambios internos en arrays u objetos. Sin embargo, es mejor práctica modificar la referencia del array en el componente en lugar de usar pipes impuros.
Manejo de errores en transform()
Es crucial implementar validación robusta en el método transform() para evitar errores en tiempo de ejecución:
@Pipe({
name: 'seguro',
standalone: true
})
export class SeguroPipe implements PipeTransform {
transform(value: any, operacion: string): any {
try {
// Validaciones de entrada
if (value == null) {
return '';
}
if (typeof value !== 'string') {
console.warn('El pipe seguro espera un string, recibió:', typeof value);
return String(value);
}
switch (operacion) {
case 'mayuscula':
return value.toUpperCase();
case 'minuscula':
return value.toLowerCase();
default:
return value;
}
} catch (error) {
console.error('Error en pipe seguro:', error);
return value; // Devolver valor original en caso de error
}
}
}
Optimización del rendimiento
Para maximizar el rendimiento de nuestros pipes personalizados, debemos seguir estas buenas prácticas:
@Pipe({
name: 'optimizado',
standalone: true
})
export class OptimizadoPipe implements PipeTransform {
// Cache para evitar recálculos innecesarios
private cache = new Map<string, string>();
transform(value: string, operacion: string): string {
if (!value) return '';
// Crear clave única para el cache
const cacheKey = `${value}-${operacion}`;
// Verificar si ya está en cache
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}
// Realizar transformación costosa
let resultado = this.procesarTexto(value, operacion);
// Guardar en cache
this.cache.set(cacheKey, resultado);
return resultado;
}
private procesarTexto(texto: string, operacion: string): string {
// Lógica compleja de transformación aquí
return texto.toLowerCase().replace(/\s+/g, '-');
}
}
Registro en componentes standalone
Para usar pipes personalizados en componentes standalone, debemos importarlos explícitamente en el array imports
:
import { Component } from '@angular/core';
import { MiPipePersonalizado } from './mi-pipe.pipe';
import { OtroPipe } from './otro-pipe.pipe';
@Component({
selector: 'app-ejemplo',
imports: [MiPipePersonalizado, OtroPipe],
template: `
<p>{{ texto | miPipePersonalizado:'parametro' }}</p>
<p>{{ numero | otroPipe }}</p>
`
})
export class EjemploComponent {
texto = 'Hola mundo';
numero = 42;
}
Es importante recordar que cada pipe debe ser importado individualmente en cada componente que lo necesite. Esta característica de los componentes standalone nos da control granular sobre las dependencias de cada componente.

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 qué es un pipe personalizado y su estructura básica en Angular.
- Implementar la interfaz PipeTransform y el método transform() para definir la lógica de transformación.
- Diferenciar entre pipes puros e impuros y conocer cuándo usar cada uno.
- Aplicar buenas prácticas para optimizar el rendimiento y manejo de errores en pipes.
- Integrar pipes personalizados en componentes standalone importándolos correctamente.