filterBy, sortBy and findBy: 3 type-safe methods to simplify the use of TypeScript arrays

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!

Most of the code we write is related to arrays and their manipulation with filtering, sorting and searching operations.

With EcmaScript 6 (ES6) the manipulation of arrays in JavaScript has been greatly simplified by introducing the use of arrow functions with the methods filter and sort.

In this article we will see how to further simplify these operations in JavaScript and, when we use TypeScript, how to do it in a type-safe way by letting the compiler report any error.

The result will be as follows:


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)

If you want to understand how it works, go to the next section, otherwise you can immediately download the script on github (about 100 lines of code) to extend the arrays with the above methods and many more:

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

Note: to enable the script, call the method extendArray () without parameters: all array objects will now have the new methods available automatically.

Alternatively, pass an array as a parameter to extend a single array: extendArray (myArray).

Later in the article it will be clear when to use one approach or the other.

Filtering before and after ES6

Before ES6, filtering a JavaScript array was done using a function. For example, given an array of numbers, to filter the numbers greater than zero we should write:


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

With ES6 the syntax has been greatly simplified (arrow function):


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

We can read the code above as: let’s filter the array for each item such that (=>) the item is greater than zero (item> 0).

Similarly with the function sort we can use an arrow function. This time it takes two arguments as input: the items to compare, two at a time. For example, suppose you want to sort the numbers of an array from the smallest to the largest, you can do it with the following code:


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

With the arrow function the syntax is definitely simplified. With this type of writing we can perform most of the filtering operations in a single line. Let’s go on instead and see how to further simplify the filtering, sorting and searching operations and how to do it in a typed way using TypeScript.

filterBy, sortBy and findBy: simplify filtering, sorting and searching in TypeScript

In C # there is a syntax, called LINQ query syntax, that allows you to manipulate the collections in a very fast and readable way. The goal of the following sections is to obtain a similar syntax in TypeScript for the most common operations on arrays, thus allowing to write less code (less code = less bug).

Arrays in JavaScript have very useful default methods. like the filter and sort methods we already saw. On this page we can find all the supported methods in ES6 (supported in almost all modern browsers):

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

These methods are more than sufficient to perform array manipulation operations.

However, what often happens to me in the code is having to manipulate an array of JSON objects and need to filter or sort the array based on a property of one of the JSON objects.

Let’s take an example to better clarify the situation. Imagine we have an array of users


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}
    ]

Suppose we need to search for users whose last name is “Rossi”:


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

This scenario is quite common: the manipulation of arrays based on the values ​​of the elements of the array (the surname Rossi in this case).

Since in the code I write every day I often have to filter based on the values ​​of a JSON field, I wondered if there is a way to further simplify the syntax. The answer is yes!

In JavaScript it is possible to extend each object with custom methods. Even an array can be extended with our own methods.

The result we want to get to is as follows:


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

The second syntax simplifies the first considerably. To achieve this, we need to extend the prototype of the Array class, that is the class descriptor:


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;
}

To filter an array with the method filterBy we can then use the function above as follows:


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

The array object should only be extended once, for example during initialization or when receiving from the server. Then we can use the filterBy method everywhere to filter based on one or more properties of the objects in the array by specifying a partial version of the JSON object itself. So for example we can filter simultaneously by name and age like this:


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

Type-safe methods in TypeScript

It’s interesting that when we use TypeScript it can be the compiler itself to prevent errors for us. The typed definition of the above method is as follows:


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

With the TypeScript code above if we use a number instead of a string to filter by name the compiler will report the error:

In TypeScript it is possible to define a type as a partial version of another type using the construct Partial.

Similarly we can define the method sortBy. Suppose we want to sort the previous array by last name. The ideal would be to get a result in which the compiler asks us if the order must be ascending or descending:


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

Using this syntax we are saying to sort the array by name from Z to A (descendant). Once again everything can be typed in TypeScript, the intellisense will suggest one of the possible values ​​”asc” or “desc” and the compiler will give errors when the syntax is wrong.

To define the sortBy method we use the following syntax:


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;
}

To achieve this type safe result we have combined three different ingredients:

  • the construct Partial as above: Partial <…>
  • the syntax for defining key-value objects {[k in keyof T]: …}
  • the syntax for saying that the value is a possible one between two strings: ‘asc’ | ‘desc’

The above code applies only to strings and numbers, which are the sorts I use most. The method can easily be extended to other types of data.

Let the TypeScript compiler know about these additional custom methods

To let the compiler know that these methods exist, the extendArray method available on Github has to be used as said in the first section. It is defined as follows:


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[];
}

At the following link it is possible to have the complete code for many other methods:

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

Note: when NOT to add custom methods to Javascript arrays

The code found at the link above allows to extend existing array objects, or the entire prototype of the Array class (just call
extendArray without passing any object as a parameter). If you proceed this way, all the arrays will always have these methods available in the code.

In general, however, it is not recommended to extend the prototype of an array with custom methods. The main reason is that anyone could override our methods. I agree with this view in all cases where third-party libraries are used. However there is a case in which I believe it is possible to extend an array with custom methods: for a private library that we use only in our projects or in our team’s projects and that is written by ourselves.

Remember:

You must be sure not to use any third-party library that overwrite the array prototype with the same method names you already used.

Conclusions

As I said above, using accessory methods for arrays allows us to simplify the code used to manipulate arrays. It is a significant improvement: the number of lines of code is reduced and the code itself is more readable. This leads to fewer errors, so fewer bugs. Moreover if bugs exist, they are located in one place.

Therefore, if it is not recommended to extend the prototype of the Array class in JavaScript, in the case of private team libraries the advantages are greater than the disadvantages. My advice is therefore to evaluate the benefits and decide whether or not to adopt this simplified syntax by implementing it based on your needs.

If the syntax above is now clear, how difficult can it be to write a type safe mapTo or groupBy method?


let names = users.mapTo('name')
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

Leave a Reply

Your email address will not be published. Required fields are marked *