Las pipes son clases que se encargan de formatear los datos para poder mostrar la información a los usuarios de una manera determinada. Este formateo sucede antes de que los datos sean recibidos por, por ejemplo, un componente.

Angular tiene pipes auto implementadas; sin embargo; también permite crear pipes personalizadas.

Empezaremos por ver cómo crear una pipe personalizada junto a cómo implementarla en distintas áreas de nuestra aplicación.

Para empezar, vamos a pintar una tabla que mostrará los platos del menú de un restaurante. Esta tabla hará uso de una lista desplegable (select) que filtrará los platos según sean parte de los primeros platos, de los segundos o del postre. Para poder filtrar los platos, crearemos una pipe personalizada.

  • Haré uso de los siguiente elementos:

Opcional: añadir Bootstrap para la apariencia

Una carpeta datamodel en la que crearé el modelo de Plato:

(plato.model.ts)

export class Plato{
    constructor(public id?:number,
                public nombre?:string,
                public price?:number,
                public category?:string,
                public img?:string){
}

}

Y esa carpeta también contendrá un fichero llamado: datasource.model.ts en el que almacenaré objetos de tipo Plato para luego mostrarlos en la tabla.

Captura de pantalla (251)

También me crearé un componente al que llamaré menuPrincipal. En su fichero .ts tendré los siguientes elementos los cuales usaré para mostrar los datos en la tabla

import { Component, OnInit } from '@angular/core';
import {Plato} from './../../datamodel/plato.model';
import {DataSource} from './../../datamodel/datasource.model';

@Component({
  selector: 'app-menuprincipal',
  templateUrl: './menuprincipal.component.html',
  styleUrls: ['./menuprincipal.component.css']
})
export class MenuprincipalComponent implements OnInit {

  public dataSource : DataSource;
  public platos: Plato[];
  
  constructor() {
    this.dataSource = new DataSource();
    this.platos = new Array<Plato>();
    this.dataSource.getData().forEach(p=> this.platos.push(p));
  }

  getPlatos():Plato[]{
    return this.platos;
  }

  ngOnInit(): void {
  }
}

En su fichero .html tendré lo siguiente:

<label>Opciones:</label>
<select class="form-control">
    <option value="">Todos</option>
    <option>Primero</option>
    <option>Segundo</option>
    <option>Postre</option>
</select>
<hr/>
<table class="table table-striped">
    <thead>
        <tr>
            <th>Nombre</th>
            <th>Categoria</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let p of getPlatos()">
            <td>{{p.nombre}}</td>
            <td>{{p.category}}</td>
            <td><a [routerLink]="['/detalles/',p.id, p.nombre, p.price, p.category,p.img]">Detalles</a></td>
        </tr>
    </tbody>
</table>

Así debería de quedarnos la aplicación al compilarla:

Captura de pantalla (253)

Ahora, me crearé un fichero dentro de la carpeta app al que llamaré: filtroCategoria.pipe.ts. En este fichero voy a crear la pipe personalizada que se encargará de filtrar por categoría:

Lo primero que se debe de hacer es traer las librerías necesarias para poder trabajar con Pipes. (Además de hacer cualquier otra importación necesaria para nuestra pipe)

import {Pipe, PipeTransform} from "@angular/core";
import {Plato} from "./datamodel/plato.model";

Lo siguiente es aplicarle decorador @Pipe en el cual se especificará el nombre para poder usarla posteriormente en una plantilla:

@Pipe({
    name:"filtroCat",
})

Ahora, dentro de la clase declararemos el método transform() Este es un método característico de las pipes y como bien dice su nombre, es el que transformará (o formateará) los datos que reciba. Es un método que deberá recibir como mínimo 1 argumento.

export class FiltroCategoria implements PipeTransform{
    transform(platos: Plato[], categoria:string):Plato[]{
        return categoria == "" ? platos 
        : platos.filter(p=> p.category == categoria);
    }
}

Para este ejemplo, recibirá un array de tipo Plato así como un string que representará la categoría a la que se quiere filtrar. Devolverá también un array de Plato. El return devuelve o bien el array completo de platos o un array de platos con sus elementos ya filtrados a la categoría que le ha llegado al método transform() por parámetros.

(código completo de la pipe)

Captura de pantalla (255)

Una vez tengamos la pipe configurada podremos aplicarla donde deseemos.

Para este caso, deberemos de usar [(ngModel)] que importaremos en app.module.ts desde FormsModule. En este fichero también haremos la importación de nuestra pipe:

Captura de pantalla (258)_LI

Entonces en el fichero menuPrincipal.component.html llamaremos a la pipe con su nombre (filtroCat) y le pasaremos “categoría” que se trata del parámetro que contendrá o bien la categoría a la que quiere filtrar o vendrá vacío (en este caso pintará todos los platos)

Las pipes siempre se declaran usando: | nombrePipe, al lado del elemento al que se quiera formatear.

<table class="table table-striped">
    <thead>
        <tr>
            <th>Nombre</th>
            <th>Categoria</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let p of getPlatos() | filtroCat: categoria">
            <td>{{p.nombre}}</td>
            <td>{{p.category}}</td>
            <td><a [routerLink]="['/detalles/',p.id, p.nombre, p.price, p.category,p.img]">Detalles</a></td>
        </tr>
    </tbody>
</table>
<select class="form-control" [(ngModel)]="categoria">
    <option value="">Todos</option>
    <option>Primero</option>
    <option>Segundo</option>
    <option>Postre</option>
</select>

En la select usando [(ngModel)] recogeremos la categoría seleccionada y le daremos a la opción “Todos” el valor vacío para que así cuando se seleccione esa opción nos muestre todos los elementos de la tabla.

<select class="form-control" [(ngModel)]="categoria">
    <option value="">Todos</option>
    <option>Primero</option>
    <option>Segundo</option>
    <option>Postre</option>
</select>

[(ngModel)]=»categoria» está enlazada con una variable en el fichero menuPrincipal.component.ts:

categoria = "";
Captura de pantalla (277)

Y de esta manera hemos creado nuestra primera pipe.

Para continuar vamos a añadir dentro del componente menuPrincipal un formulario para insertar un nuevo plato junto con su correspondiente método. Esto nos servirá para explicar el concepto de pipes puras y pipes impuras.

<h3>Insertar nuevo plato</h3>
<form class="form-group" (ngSubmit)="addPlato(newPlato)">
    <label>Id:</label>
    <input type="number"
    class="form-control"
    name="id"
    [(ngModel)]="newPlato.id"/>
    <br/>
    <label>Nombre:</label>
    <input type="text"
    class="form-control"
    name="nombre"
    [(ngModel)]="newPlato.nombre"/>
    <br/>
    <label>Precio:</label>
    <input type="number"
    class="form-control"
    name="price"
    [(ngModel)]="newPlato.price"/>
    <br/>
    <label>Categoria:</label>
    <select 
    class="form-control"
    name="category"
    [(ngModel)]="newPlato.category">
    <option>Primero</option>
    <option>Segundo</option>
    <option>Postre</option>
    </select>
    <br/>
    <label>Imagen</label>
    <input type="string"
    class="form-control"
    name="img"
    [(ngModel)]="newPlato.img"/>
    <button class="btn btn-warning">Insertar</button>
</form>
  addPlato(p: Plato){
    console.log(p);
    this.platos.push(p);
    console.log(this.platos);
  }
Captura de pantalla (260)
  • Pipes puras vs pipes impuras:

Primeramente, en la pipe creada vamos a indicarle lo siguiente:

@Pipe({
    name:"filtroCat",
    pure:true
})

Con esto le estamos indicando que nuestra pipe es pura. Ahora, usando la select vamos a seleccionar una de las categorías para que nos muestre sus elementos y directamente insertar un nuevo objeto plato perteneciente a la categoría seleccionada la lista desplegable.

Captura de pantalla (261)

Gracias al console.log podemos ver que nuestro nuevo objeto se ha insertado; en cambio, en la tabla no se muestra el cambio al momento. Si queremos ver nuestro objeto nuevo, deberemos de seleccionar “Todos”.

Esto sucede porque la pipe esta declarada como pura y, por tanto, no acepta cambios. Para solucionar este problema declararemos la pipe como impura:

@Pipe({
    name:"filtroCat",
    pure:false
})

Al repetir la misma acción, veremos que al insertar nuestro nuevo objeto, se actualiza la tabla al momento.

  • Usar Pipes con el click de un botón:

También es útil saber que una pipe se puede aplicar al click de un botón siguiendo los siguientes pasos.

Primero, voy a añadir a la tabla un link “Detalles”. Al pulsar en él, nos enseñará los detalles del correspondiente plato pulsado. Para ello, me crearé un componente al que llamaré detalles y al cual le aplicaré routing.

Así debería de quedar:

Captura de pantalla (264)

Este componente además de tener los detalles del plato seleccionado, tendrá un botón que transformará el precio a libras cuando se pulse sobre él. Esto lo hará a través de una pipe.

Siguiendo los pasos anteriores, nos crearemos nuestra pipe personalizada. Aunque esta vez, no la declararemos en la plantilla html del componente, sino que se hará de una manera un tanto diferente.

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
    name:"convertLibras"
})

export class ConversorALibras implements PipeTransform{

    transform(precio:number):number{
        const libraVal = 0.90;
        return precio * libraVal;
    }

}

En la plantilla html tendremos el botón que hará referencia a un método en el fichero .ts del componente el cual se encargará de llamar a la pipe para que transforme los datos. Le llegará el parámetro del precio necesario para luego pasárselo a la pipe.

Además tendremos un variable “{{mensaje}}” en la que se mostrará el precio transformado a libras.

Captura de pantalla (266)_LI

En el fichero .ts del componente vamos importar la pipe y dentro del decorador @Component() declaremos como provider la pipe recién importada:

Captura de pantalla (269)_LI

En el constructor de este componente declararemos la pipe como una variable privada. Y creamos el método al que llamará el botón. Es en este método al que llamaremos a la pipe:

Captura de pantalla (273)

Declaramos la variable “mensaje” para luego almacenar el contenido devuelto por la pipe.

Como se ve, usando la variable declarada en el constructor podemos hacer uso del método transform() propio de las pipes y al cual le pasamos el parámetro que necesita para realizar su función. Esto se iguala a la variable mensaje.

Captura de pantalla (277)_LI

Si no fijamos en la plantilla html del componente detalles podemos distinguir los siguiente:

 <tr><td>{{mensaje | currency:"GBP":"£"}}</td></tr>

Lo que se usa aquí se trata de una de las pipes auto implementadas que proporciona Angular. En este caso la pipe usada es “currency” que formatea cantidades monetarias.

Para implementarla únicamente debemos de colocarla junto al dato que deseemos formatear y añadirle a qué moneda deseamos formatear con su correspondiente símbolo.

Además de “currency” tenemos otras pipes que podemos usar, alguna de ellas las implementaremos en nuestra aplicación.

uppercase/lowercase: formatea los datos a mayúscula o a minúscula respectivamente

<h3>Detalles de: {{plato.nombre | uppercase}}</h3>
Captura de pantalla (278)
  • date: formatea las fechas.

Esta pipe trae diferentes formatos dependiendo de cómo queramos mostrar la fecha. Además de que es sensible a localización que por defecto está en Estados Unidos. Para cambiarla se deberá de colocar el siguiente codigo en el fichero: app.module,ts

Captura de pantalla (280)_LI

Si quisiéramos añadir la fecha de hoy a nuestra aplicación procederíamos así:

export class AppComponent {
  dateObject: Date = new Date(2020, 10, 16);
  dateString: string = "2020-11-16T00:00:00.000Z";
  dateNumber: number = 1582156800000;

}

Los tres formato son válidos. La diferencia es que el primero es un objeto de tipo Date, el segundo es un string y el tercero usa valores numéricos representando milisegundos desde 1970.

Ahora en el html debemos de escribir lo siguiente:

<h2>{{dateString | date}}</h2>

El formato que se haya decidido usar junto con la pipe date para que haga el formateo.

Captura de pantalla (281)

Otras pipes autoimplementadas son:

  • number: realiza formateos de valores numéricos. Al igual que date, es sensible a la localización.
  • percent: se encarga de formatear los porcentajes
  • json: transforma un objeto a una cadena json
  • slice: selecciona los elementos de un array o de una cadena
  • async: esta pipe se suscribe a un observable o una promesa para devolver el valor más actualizado.

Con esto, deberíamos ser capaces de crear e implementar Pipes en nuestra aplicación de Angular y así transformar los datos de una manera más eficaz.

Autora: Aurora Moreno López

Curso: Desarrollo Web Full Stack, MultiCloud y Multiplataforma

Centro: Tajamar

Año académico: 2020-2021

Enlace a GitHub: https://github.com/auroraMoreno/angularpipes.git

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.