← maurobernal.com.ar

Pipes de Angular Listos para Usar: Cómo ngx-transforms Te Salva el Día

Si llevas un tiempo trabajando con Angular, ya sabes de memoria la situación: necesitás mostrar una fecha relativa («hace 3 días»), truncar un texto largo, filtrar un array de objetos, o formatear bytes en algo legible. Y cada vez terminás buscando en Stack Overflow, escribiendo la misma lógica de siempre, o creando un pipe custom que ya hiciste en tres proyectos anteriores.

¿Y si todos esos pipes ya estuvieran listos para usar? Eso es exactamente lo que propone ngx-transforms: una colección de pipes de Angular listos para importar y usar en tu template, sin escribir una línea de lógica extra.

¿Qué es ngx-transforms?

ngx-transforms es una librería de pipes para Angular que reúne las transformaciones más comunes que cualquier desarrollador necesita en el día a día. La idea es simple: en lugar de implementar la misma lógica una y otra vez en cada proyecto, la instalás una sola vez y la tenés disponible en todos tus componentes.

npm install ngx-transforms --save

Una vez instalada, podés importar los pipes directamente en tus componentes standalone o en tu módulo, y usarlos directamente en el template.

El dolor que resuelve: el ciclo del pipe custom

Todo Angular developer ha vivido esta secuencia al menos una vez (o veinte):

  1. Necesitás mostrar «hace 5 minutos» en lugar de un timestamp
  2. Angular no tiene ese pipe built-in
  3. Creás time-ago.pipe.ts con tu propia implementación
  4. Dos proyectos después, hacés copy-paste de ese mismo archivo
  5. En el tercer proyecto, el copy-paste tiene un bug que ya habías corregido en el original

Con una librería de pipes como ngx-transforms, este ciclo desaparece. La lógica está testeada, mantenida y disponible con un simple import.

Pipes de texto: los que más vas a usar

La manipulación de strings es una de las tareas más frecuentes en cualquier UI. Algunos pipes que te cambian la vida:

<!-- Truncar texto largo con elipsis -->
{{ descripcion | truncate:100 }}
{{ descripcion | truncate:100:'...' }}

<!-- Primera letra en mayúscula (no toda la palabra) -->
{{ 'hola mundo' | ucFirst }}
<!-- Output: "Hola mundo" -->

<!-- Slugify para URLs amigables -->
{{ 'Mi Artículo de Blog' | slugify }}
<!-- Output: "mi-articulo-de-blog" -->

<!-- Camelcase -->
{{ 'nombre_de_variable' | camelize }}
<!-- Output: "nombreDeVariable" -->

<!-- Repetir texto -->
{{ '⭐' | repeat:5 }}
<!-- Output: "⭐⭐⭐⭐⭐" -->

<!-- Limpiar HTML tags de un string -->
{{ contenidoHTML | stripTags }}

<!-- Pluralizar automáticamente -->
{{ cantidad }} {{ 'comentario' | makePluralString:cantidad }}

Todos estos casos surgen constantemente en el desarrollo de interfaces: cards con texto limitado, badges, slugs para rutas, labels dinámicos. Tenerlos como pipe evita lógica en el componente y mantiene el template limpio y declarativo.

Pipes de fechas: adiós al boilerplate de Moment.js

El pipe más pedido que Angular no trae de fábrica: fechas relativas.

<!-- Fechas relativas (timeAgo) -->
{{ post.createdAt | timeAgo }}
<!-- Output: "hace 3 minutos", "ayer", "la semana pasada" -->

<!-- Comparado con el enfoque manual sin pipe -->
<span>{{ getTimeAgo(post.createdAt) }}</span>
<!-- ☝️ Requiere lógica en el componente, no es reactive, es más difícil de testear -->

El pipe timeAgo es una alternativa liviana a Moment.js o date-fns para este caso de uso puntual. Se integra perfectamente con el sistema de Change Detection de Angular y no requiere importar una librería de fechas completa.

Pipes de arrays: filtrar, ordenar y agrupar en el template

Uno de los casos de uso más poderosos: manipular colecciones directamente en el template sin procesar datos en el componente.

<!-- Filtrar array de objetos -->
@for (user of usuarios | filterBy:['activo']:true; track user.id) {
  <app-user-card [user]="user" />
}

<!-- Ordenar por propiedad -->
@for (producto of productos | orderBy:'precio'; track producto.id) {
  <app-producto [data]="producto" />
}

<!-- Ordenar descendente -->
@for (nota of notas | orderBy:'-fecha'; track nota.id) {
  <app-nota [nota]="nota" />
}

<!-- Agrupar por categoría -->
@for (grupo of productos | groupBy:'categoria' | keyvalue; track grupo.key) {
  <h3>{{ grupo.key }}</h3>
  @for (item of grupo.value; track item.id) {
    <app-producto [data]="item" />
  }
}

<!-- Valores únicos -->
{{ tags | unique | json }}

<!-- Aplanar array anidado -->
{{ matrizDeNumeros | flatten }}

Nota sobre performance: Los pipes de array (filterBy, orderBy) suelen ser impure por necesidad, lo que significa que Angular los re-evalúa en cada ciclo de detección de cambios. Para listas grandes, considerá cachear el resultado en el componente con computed() si usás Signals, o con una propiedad derivada.

Pipes matemáticos: cálculos sin tocar el componente

Pequeños cálculos que terminan siendo métodos en el componente cuando deberían ser pipes:

<!-- Suma de un array -->
Total: {{ precios | sum | currency:'ARS' }}

<!-- Promedio -->
Promedio: {{ calificaciones | average }}

<!-- Máximo y mínimo -->
Mayor: {{ valores | max }}
Menor: {{ valores | min }}

<!-- Porcentaje -->
{{ completados | percentage:total }}%

<!-- Formatear bytes a KB/MB/GB legible -->
{{ archivo.size | bytes }}
<!-- Output: "2.4 MB" en lugar de "2516582" -->

<!-- Redondear -->
{{ precio | round:2 }}
{{ temperatura | ceil }}
{{ descuento | floor }}

Pipes booleanos: legibilidad en los templates

Una categoría que parece pequeña pero mejora mucho la legibilidad del código:

<!-- Verificaciones de tipo -->
@if (valor | isString) {
  <span>{{ valor }}</span>
}

@if (datos | isArray) {
  <ul>...</ul>
}

<!-- Comparaciones -->
@if (precio | isGreaterThan:1000) {
  <span class="premium">Premium</span>
}

@if (stock | isLessEqualThan:5) {
  <span class="warning">¡Últimas unidades!</span>
}

Antes y después: un ejemplo real

Imaginá una card de producto que muestra precio, nombre, descripción y stock. Así se ve sin pipes de utilidad:

// producto-card.component.ts (sin pipes)
export class ProductoCardComponent {
  @Input() producto!: Producto;

  get descripcionCorta(): string {
    return this.producto.descripcion.length > 120
      ? this.producto.descripcion.substring(0, 120) + '...'
      : this.producto.descripcion;
  }

  get stockLabel(): string {
    return this.producto.stock === 1 ? '1 unidad' : ${this.producto.stock} unidades;
  }

  get precioFormateado(): string {
    return new Intl.NumberFormat('es-AR', { style: 'currency', currency: 'ARS' }).format(this.producto.precio);
  }
}

Y así se ve con pipes de utilidad:

<!-- producto-card.component.html (con pipes) -->
<div class="card">
  <h3>{{ producto.nombre | ucFirst }}</h3>
  <p>{{ producto.descripcion | truncate:120 }}</p>
  <span class="price">{{ producto.precio | currency:'ARS' }}</span>
  <span>{{ producto.stock }} {{ 'unidad' | makePluralString:producto.stock }}</span>

  @if (producto.stock | isLessEqualThan:5) {
    <span class="badge-warning">¡Últimas unidades!</span>
  }
</div>

El componente quedó sin lógica de presentación. Todo está en el template, declarativo, legible y testeable.

Pipes de objetos: iterar y transformar

<!-- Obtener keys de un objeto -->
@for (key of configObj | keys; track key) {
  <span>{{ key }}: {{ configObj[key] }}</span>
}

<!-- Obtener values -->
@for (val of configObj | values; track val) {
  <li>{{ val }}</li>
}

<!-- Seleccionar solo algunas propiedades -->
{{ usuario | pick:['nombre', 'email'] | json }}

<!-- Excluir propiedades sensibles -->
{{ usuario | omit:['password', 'token'] | json }}

¿Por qué usar una librería de pipes en lugar de crearlos?

  • Testeados: Los pipes de una librería tienen tests unitarios. Los tuyos custom… a veces.
  • Mantenidos: Las actualizaciones de Angular se manejan en la librería, no en tu código.
  • Consistentes: El mismo comportamiento en todos tus proyectos.
  • Documentados: No hay que leer el código para entender qué hace slugify.
  • Sin dependencias externas: Librerías como ngx-transforms funcionan de forma autónoma, sin necesidad de Moment.js ni lodash.
  • Tree-shakeable: Solo los pipes que importás llegan al bundle final.

Integración con Angular Signals

Los pipes funcionan perfectamente con el nuevo modelo reactivo de Angular Signals. Podés usar pipes en templates que consumen signals sin ninguna fricción:

export class ProductosComponent {
  productos = signal<Producto[]>([]);
  busqueda = signal('');
  ordenamiento = signal('nombre');
}

<!-- Template: todo declarativo, todo reactivo -->
@for (
  p of productos() | filterBy:['nombre']:busqueda() | orderBy:ordenamiento();
  track p.id
) {
  <app-producto-card [producto]="p" />
}

Conclusión

Los pipes de utilidad son uno de esos recursos que, una vez que los incorporás a tu flujo de trabajo, no podés creer cómo los viviste sin ellos. Reducen la lógica en los componentes, hacen los templates más expresivos y eliminan el copy-paste entre proyectos.

ngx-transforms es una de las alternativas disponibles en el ecosistema Angular para resolver exactamente este problema. La próxima vez que vayas a escribir getTimeAgo(), truncateText() o formatBytes() en tu componente, considerá si un pipe de utilidad no sería la solución más limpia.

Tu yo del futuro —el que tenga que mantener ese código— te lo va a agradecer.