filterBy, sortBy e findBy: 3 metodi tipizzati per semplificare l’uso degli array in TypeScript

Prima di iniziare, vuoi imparare Angular?
Angular Corso completo + 10 esempi da casi d'uso reali

Il libro definitivo per imparare Angular, con esempi, codici e l'esperienza che serve per essere efficaci nello sviluppare siti, dashboard CRUD o web-app mobile (Raccomandato con un punteggio di 5 stelle su Amazon)

Info sul libro + Codici e demo delle esercitazioni Libro stampato Kindle
Design Pattern in Typescript + 2 progetti reali

C'è una differenza tra programmare e progettare un software: il design. In questo libro viene mostrato il processo per arrivare ad un design semplice e manutenibile attraverso i design pattern

Info sul libro + Codici delle esercitazioni Libro stampato Kindle
Ok, iniziamo!

Gran parte del codice che scriviamo è relativo agli array e alla loro manipolazione con operazioni di filtraggio, ordinamento e ricerca.

Con EcmaScript 6 (ES6) la manipolazione degli array in JavaScript è stata molto semplificata introducendo l’uso delle arrow function con i metodi filter, sort.

In questo articolo vedremo come semplificare ulteriormente queste operazioni in JavaScript e, quando usiamo TypeScript, come farlo in maniera tipizzata facendo si che il compilatore ci segnali eventuali errori.

Il risultato sarà il seguente:


let users = extendArray([
        {name: "Salvatore", surname: "Romeo", age: 34},
        {name: "Mario", surname: "Rossi", age: 30},
        {name: "Luca", surname: "Rossi", age: 30},
        {name: "Giuseppe", surname: "Ferri", age: 40},
        {name: "Giuseppe", surname: "Mauri", age: 40}
    ])



    let RossiUsers = users.filterBy({surname: "Rossi"})
    console.log(RossiUsers)

    let Giuseppe40Users = users.filterBy({name: "Giuseppe", age: 40})
    console.log(Giuseppe40Users)

    let sortedBySurname = users.sortBy({surname: "desc"})
    console.log(sortedBySurname)

Se vuoi comprendere come funziona il meccanismo, vai alla prossima sezione, altrimenti scarica subito lo script su github (circa 100 righe di codice) per estendere gli array con i metodi di cui sopra:

https://github.com/devexp-io/post-codes/blob/master/src/typescript-array-extensions/array.extension.ts

Nota: per abilitare lo script, richiama il metodo
extendArray() senza parametri: tutti gli oggetti array avranno ora i nuovi metodi disponibili automaticamente.

In alternativa passa come parametro un array per estendere un singolo array: extendArray(myArray).

Più avanti sarà chiaro quando usare un approccio piuttosto che l’altro.

Filtraggio prima e dopo ES6

Prima di ES6 il filtraggio di un array JavaScript veniva fatto utilizzando una function. Dato un array di numeri, per filtrare ad esempio i numeri maggiori di zero bastava scrivere:


var newArray = array.filter(function(item) {
  return item > 0;
})

Con ES6 la sintassi è stata molto semplificata:


let newArray = array.filter(item => item > 0)

Possiamo leggere il codice sopra come: filtra l’array per ogni item tale che (=>) l’item è maggiore di zero (item > 0).

Analogamente per la funzione sort possiamo usare una arrow function, che stavolta prende in input due argomenti: gli item da confrontare, due alla volta. Supponiamo ad esempio di voler ordinare i numeri di un array dal più piccolo al più grande con una nostra funzione:


array.sort( (item1, item2) => item1 > item2? 1: -1)

Con le arrow function la sintassi è decisamente semplificata. Con questo tipo di scrittura possiamo eseguire la maggior parte delle operazioni di filtraggio in un’unica linea.

Andiamo invece a capire come semplificare ulteriormente le operazioni di filtraggio, ordinamento e ricerca e come farlo in maniera tipizzata utilizzando TypeScript.

filterBy, sortBy e findBy: semplificare filtraggio, ordinamento e ricerca in TypeScript

In C# esiste una sintassi, detta LINQ query syntax, che permette di manipolare le collezioni in maniera molto rapida e leggibile. L’obiettivo delle sezioni che seguono è di ottenere una sintassi simile in TypeScript per le operazioni più comuni sugli array, permettendo quindi di scrivere meno codice (meno codice = meno bug).

In JavaScript gli array hanno dei metodi di default molto utili. In particolare abbiamo visto i metodi filter e sort, ma ne esistono molti altri. In questa pagina possiamo trovare tutti i metodi supportati in ES6 (quindi da quasi tutti i browser recenti):

https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Questi metodi sono più che sufficienti per svolgere molte operazioni di manipolazione degli array.

Tuttavia, quello che mi capita spesso nel codice, è di dover manipolare un array di oggetti JSON e di avere necessità di filtrare o ordinare l’array in base ad una proprietà di uno degli oggetti.

Facciamo un esempio per chiarire meglio la situazione. Immaginiamo di avere un array di utenti


let users = [
        {name: "Salvatore", surname: "Romeo", age: 34},
        {name: "Mario", surname: "Rossi", age: 30},
        {name: "Luca", surname: "Rossi", age: 30},
        {name: "Giuseppe", surname: "Ferri", age: 40},
        {name: "Michele", surname: "Mauri", age: 50}
    ]

Supponiamo di dover cercare gli utenti il cui cognome è “Rossi”:


let RossiUsers = users.filter(user => user.surname == "Rossi")

Questo scenario è abbastanza comune: la manipolazione di array in base ai valori degli elementi dell’array (il cognome Rossi in questo caso).

Poiché nel codice che scrivo ogni giorno mi capita spesso di dover filtrare in base ai valori di un campo, mi sono chiesto se c’è un modo per semplificare ulteriormente la sintassi. La risposta è si!

In JavaScript è possibile estendere ogni oggetto con dei metodi custom. Anche un array è estendibile con dei metodi nostri.

L’obiettivo a cui vogliamo arrivare è il seguente:


let RossiUsers = users.filter(user => user.surname == "Rossi") // sintassi ES6
    RossiUsers = users.filterBy({surname: "Rossi"}) // obiettivo :-)

La seconda sintassi semplifica la prima notevolmente. Per ottenere questo risultato dobbiamo estendere il prototipo della classe Array, cioè il descrittore della classe:


function extendArray(targetArray){
targetArray.filterBy= function(e) {

      return this.filter(item => {
        let include = true;
        for (let k in e) {
          include = include && item[k] == e[k];
        }
        return include;
      });
    };
return targetArray;
}

Per filtrare un array con il metodo filterBy possiamo quindi usare la funzione sopra come segue:


let extendedArray = extendArray(users);
extendedArray.filterBy({name: "Salvatore"})

L’oggetto array va esteso solo una volta, ad esempio in fase di inizializzazione o di ricezione dal server. Poi possiamo usare il metodo filterBy ovunque per filtrare in base ad una o più proprietà degli oggetti nell’array specificando una versione parziale dell’oggetto JSON stesso, quindi ad esempio possiamo filtrare contemporaneamente per nome e età:


let Giuseppe40Users = users.filterBy({name: "Giuseppe", age: 40})

Tipizzazione in TypeScript

La cosa ancora più interessante è che quando usiamo TypeScript sarà il compilatore stesso a prevenire errori per noi. La definizione tipizzata del metodo sopra è la seguente:


...
filterBy = function (e: Partial): T[] {
...

Con il codice TypeScript sopra se usiamo un numero al posto di una stringa per filtrare per nome il compilatore ci segnalerà l’errore:

In TypeScript infatti è possibile definire un tipo come una versione parziale di un altro tipo usando il costrutto Partial.

Analogamente possiamo definire il metodo sortBy. Supponiamo di voler ordinare l’array precedente per cognome. L’ideale sarebbe ottenere un risultat in cui il compilatore ci chiede se l’ordinamento deve essere ascendente o discendente:


let sortedBySyrname = users.sortBy({name: "desc"})

In questo modo stiamo dicendo di ordinare l’array per nome dalla Z alla A (discendente). Ancora una volta il tutto può essere tipizzato in TypeScript, l’intellisense ci suggerirà uno dei possibili valori “asc” o “desc” e il compilatore darà errore se dovessimo scrivere male la sintassi.

Per definire il metodo sortBy usiamo la seguente sintassi:


targetArray.sortBy = function (e: Partial<{ [k in keyof T]: 'asc' | 'desc' }>): T[] {
      let key = '';
      let sorting = 1;
      for (let ekey in e) {
        key = ekey;
        sorting = e[ekey] == 'asc' ? 1 : -1;
      }
      return this.sort((a1, a2) => {

        if (!a1) return 1;
        if (!a1[key]) return 1;
        if (!a2) return -1;
        if (!a2[key]) return 1;
        if (isNumeric(a1[key]) && isNumeric(a2[key]))
          return (a1[key] - a2[key]) * sorting;
        if (isString(a1[key]) && isString(a2[key]))
          return a1[key].toLowerCase().localeCompare(a2[key].toLowerCase()) * sorting;

      });
    };
function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

function isString(s) {
  return typeof s === 'string' || s instanceof String;
}

Per ottenere questo risultato type safe abbiamo combinato tre diversi ingfredienti:

  • il costrutto Partial come sopra Partial<…>
  • la sintassi per definire oggetti chiave valore { [k in keyof T]: … }
  • la sintassi per dire che il valore è una possibile tra due stringhe: ‘asc’ | ‘desc’

Il codice sopra vale solo per stringhe e numeri, che sono i sort che uso maggiormente. Il metodo può facilmente essere esteso per altre tipologie di dato.

Far sapere al compilatore TypeScript che esistono questi ulteriori metodi custom

Per far sapere al compilatore che esistono anche questi metodi, il metodo extendArray è così definito:


export function extendArray(target:Array = Array.prototype):Array & ArrayExtended {
...
}

interface ArrayExtended {
  removeLast(): T;

  empty(): boolean;

  contains(e: T): boolean;

  findBy(e: Partial): T[];

  filterBy(e: Partial): T[];

  sortBy(e: Partial<{ [k in keyof T]: 'asc' | 'desc' }>): T[];

  ifFound(action: (first: T) => void): T[];

  ifEmpty(action: () => void): T[];
}

Al seguente link è possibile avere il codice completo per molti altri metodi:

https://github.com/devexp-io/post-codes/blob/master/src/typescript-array-extensions/array.extension.ts

Nota: quando NON aggiungere metodi custom agli array Javascript

Il codice reperibile al link sopra permette di estendere oggetti array esistenti, o l’intero prototipo della classe Array (basta chiamare il metodo una tantum nel codice senza passare nessun oggetto come parametro). Se si procede per questa strada, tutti gli array avranno disponibili questi metodi sempre e comunque nel codice.

In generale però è sconsigliato estendere il prototipo di un array con dei metodi custom. La motivazione principale è che chiunque potrebbe sovrascrivere i nostri metodi. Concordo con questa visione in tutti i casi in cui si usano librerie di terze parti. Tuttavia c’è un caso in cui a mio avviso è possibile estendere un array con dei metodi custom: per una libreria che usiamo solo nei nostri progetti o nei progetti del nostro team.

Devo avere soltanto un accorgimento importante:

Devo essere sicuro di non usare librerie di terze parti che sovrascrivino l’array con gli stessi nomi usati da me

Conclusioni

Come dicevo sopra, usare metodi accessori per gli array ci permette di semplificare il codice usato nella manipolazione degli array stessi. Non è una cosa da poco: il numero di righe di codice si riduce infatti sensibilmente ed è più leggibile. Ciò porta a fare meno errori, quindi ad avere meno bug ed eventuali bug sono comunque concentrati tutti in un unico punto.

Se quindi è sconsigliato estendere il prototipo della classe Array in JavaScript, nel caso di librerie interne al team i vantaggi sono maggiori degli svantaggi. Il mio consiglio è quindi di valutare bene i benefici e decidere se adottare o no questa sintassi semplificata.

Ti è piaciuto l'articolo e vuoi approfondire questi argomenti?
Angular Corso completo + 10 esempi da casi d'uso reali

Il libro definitivo per imparare Angular, con esempi, codici e l'esperienza che serve per essere efficaci nello sviluppare siti, dashboard CRUD o web-app mobile (Raccomandato con un punteggio di 5 stelle su Amazon)

Info sul libro + Codici e demo delle esercitazioni Libro stampato Kindle
Design Pattern in Typescript + 2 progetti reali

C'è una differenza tra programmare e progettare un software: il design. In questo libro viene mostrato il processo per arrivare ad un design semplice e manutenibile attraverso i design pattern

Info sul libro + Codici delle esercitazioni Libro stampato Kindle
Grazie!


Newsletter

* indicates required

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *