TypeScript Remove elements from an object array

eye-catchJavaScript/TypeScript
Sponsored links
Sponsored links

Remove a single element from an array

pop method to remove the last element

If you want to remove the last element, pop method can be used for all data types.

{
    const array = [5, 4, 3, 2, 1, 0, 9];
    console.log(array.pop()) // 9
    console.log(array) // [ 5, 4, 3, 2, 1, 0 ]
}
{
    const array = ["aa", "bb", "cc"];
    console.log(array.pop()) // cc
    console.log(array) // ["aa", "bb"]
}

shift method to remove the first element

In opposite, shift method removes the first element.

{
    const array = [5, 4, 3, 2, 1, 0, 9];
    console.log(array.shift()) // 5
    console.log(array) // [ 4, 3, 2, 1, 0, 9 ]

}
{
    const array = ["aa", "bb", "cc"];
    console.log(array.shift()) // aa
    console.log(array) // [ 'bb', 'cc' ]
}

Combination of indexOf and splice

If you want to remove an element in the middle of the array, you need to first know the index of the target element. indexOf method returns the index if it exists in the array.

Then, remove it with splice method. The second argument is always 1 in this case. The second argument is the number of elements that will be deleted.

{
    const array = [5, 2, 1, 0, 2];
    const index = array.indexOf(2);
    console.log(index) // 1
    array.splice(index, 1);
    console.log(array) // [ 5, 1, 0, 2 ]
}
{
    const array = ["aa", "bb", "cc"];
    const index = array.indexOf("bb");
    console.log(index); // 1
    array.splice(index, 1);
    console.log(array) // [ 'aa', 'cc' ]
}

But as you can see, it deletes only the first occurrence. Note that this is an exact match. If you write array.indexOf("b") to search a string that contains “b”, it doesn’t work as expected.

Sponsored links

Remove multiple elements from an array

indexOf loop to remove multi occurrences

If you want to delete multiple elements, it needs to be done in a loop.

{
    const array = [5, 2, 1, 0, 2];
    for (let i = 0; ; i++) {
        const index = array.indexOf(2);
        console.log(`${i}: ${index}`)
        // 0: 1
        // 1: 3
        // 2: -1
        if (index < 0) {
            break;
        }
        array.splice(index, 1);
    }
    console.log(array)
    // [ 5, 1, 0 ]
}
{
    const array = ["aa", "bb", "cc", "bb"];
    for (let i = 0; ; i++) {
        const index = array.indexOf("bb");
        console.log(`${i}: ${index}`)
        // 0: 1
        // 1: 2
        // 2: -1
        if (index < 0) {
            break;
        }
        array.splice(index, 1);
    }
    console.log(array)
    // [ 'aa', 'cc' ]
}

The second statement, where the loop condition is normally defined, in for is empty because the length of the array changes when deleting an element from the array.

Instead, it checks whether the array still has the target value or not. indexOf method returns -1 if the target value is not found in the array. If the target value is not found anymore, it breaks the loop.

Compare the value one by one in a for loop

I think the previous way is not so good if the array is very big. I guess for-loop is used in indexOf to find the index. If there are multiple elements to delete, it calls it multiple times. It might cause a performance issue.

It seems not to be the case. See ECMScript.

Let’s implement it in for-loop here.

{
    const array = [2, 2, 1, 0, 2];
    for (let i = array.length - 1; i >= 0; i--) {
        if (array[i] === 2) {
            console.log(i)
            // 4
            // 1
            // 0
            array.splice(i, 1);
        }
    }
    console.log(array)
    // [ 1, 0 ]
}
{
    const array = ["aa", "bb", "cc", "bb"];
    for (let i = array.length - 1; i >= 0; i--) {
        if (array[i] === "bb") {
            console.log(i)
            // 3
            // 1
            array.splice(i, 1);
        }
    }
    console.log(array)
    // [ 'aa', 'cc' ]
}

It starts from the last element because the length of the array changes when deleting an element from the array. If it starts from the end, it doesn’t affect the result.

This is an NG implementation.

// NOT WORK as expected
const array = [2, 2, 1, 0, 2];
for (let i = 0; i < array.length; i++) {
    if (array[i] === 2) {
        console.log(i)
        // 0
        // 3
        array.splice(i, 1);
    }
}
console.log(array)
// [ 2, 1, 0 ] 

How to remove an element from an object array

indexOf for exact match

indexOf is not so useful for an object because it is an exact match.

const obj = { prop1: 4, prop2: "d" };
const array = [
    { prop1: 1, prop2: "a" },
    { prop1: 2, prop2: "b" },
    { prop1: 3, prop2: "c" },
];
array.push(obj);
const notFound = array.indexOf({ prop1: 1, prop2: "a" });
console.log(notFound);  // -1
const index = array.indexOf(obj);
console.log(index); // 3
array.splice(index, 1);
console.log(array);
// [
//     { prop1: 1, prop2: 'a' },
//     { prop1: 2, prop2: 'b' },
//     { prop1: 3, prop2: 'c' }
// ]

For the first indexOf call, it doesn’t find the element even though the values are the same as the first element. The value is the same but the object itself is different.

If you want to delete the object, you need to pass precisely the same object as you can see on the second indexOf call.

findIndex for loose match

Use findIndex instead if you can’t get precisely the same object.

const array = [
    { prop1: 1, prop2: "a" },
    { prop1: 2, prop2: "b" },
    { prop1: 3, prop2: "c" },
    { prop1: 4, prop2: "d" }
];
const index = array.findIndex((value) => value.prop1 === 3 && value.prop2 === "c");
console.log(index); // 2
array.splice(index, 1);
console.log(array);
// [
//     { prop1: 1, prop2: 'a' },
//     { prop1: 2, prop2: 'b' },
//     { prop1: 4, prop2: 'd' }
// ]

You can define which properties should be compared to find the element.

How to remove multiple elements from an object array

If you need to remove multiple elements from an object array, you need to remove them in a for-loop. Of course, findIndex can be used here but let’s not use it here.

It’s basically the same as number/string version.

const array = [
    { prop1: 1, prop2: "a" },
    { prop1: 2, prop2: "b" },
    { prop1: 3, prop2: "c" },
    { prop1: 4, prop2: "d" },
    { prop1: 2, prop2: "bb" },
];
for (let i = array.length - 1; i >= 0; i--) {
    if (array[i].prop1 === 2) {
        console.log(i)
        // 4
        // 1
        array.splice(i, 1);
    }
}
console.log(array);
// [
//   { prop1: 1, prop2: 'a' },
//   { prop1: 3, prop2: 'c' },
//   { prop1: 4, prop2: 'd' }
// ]

Add remove methods to Array interface (prototype)

If you often remove an element in many places, it’s better to define the function in the Array interface in the following way so that you can simply call the methods like array.remove().

declare global {
    interface Array<T> {
        removeByPerfectMatch(value: T): void;
        removeByLooseMatch(predicate: (value: T, index: number, obj: T[]) => unknown): void;
        removeAllByPerfectMatch(value: T): void;
        removeAllByLooseMatch(predicate: (value: T, index: number, obj: T[]) => unknown): void;
    }
}

Array.prototype.removeByPerfectMatch = function (value) {
    const index = this.indexOf(value);
    if (index >= 0) {
        this.splice(index, 1);
    }
}

Array.prototype.removeByLooseMatch = function (predicate) {
    const index = this.findIndex(predicate);
    if (index >= 0) {
        this.splice(index, 1);
    }
}

Array.prototype.removeAllByPerfectMatch = function (value) {
    for (let i = this.length - 1; i >= 0; i--) {
        if (this[i] === value) {
            this.splice(i, 1);
        }
    }
}

Array.prototype.removeAllByLooseMatch = function (predicate) {
    for (let i = this.length - 1; i >= 0; i--) {
        if (predicate(this[i], i, this)) {
            this.splice(i, 1);
        }
    }
}

The usage is the same as the other methods. You can call those methods after a dot.

const obj = { prop1: 444, prop2: "ddd" };
const array = [
    { prop1: 111, prop2: "aaa" },
    { prop1: 222, prop2: "bbb" },
    { prop1: 333, prop2: "ccc" },
    { prop1: 222, prop2: "bbb" },
    { prop1: 444, prop2: "ddd" },
    { prop1: 222, prop2: "bbb" },
];
array.push(obj);
array.push(obj);
array.push(obj);

let before = array.length;
array.removeByPerfectMatch(obj);
console.log(`length: ${before} -> ${array.length} (removeByPerfectMatch)`);
console.log(array);
// length: 9 -> 8 (removeByPerfectMatch)
// [
//   { prop1: 111, prop2: 'aaa' },
//   { prop1: 222, prop2: 'bbb' },
//   { prop1: 333, prop2: 'ccc' },
//   { prop1: 222, prop2: 'bbb' },
//   { prop1: 444, prop2: 'ddd' },
//   { prop1: 222, prop2: 'bbb' },
//   { prop1: 444, prop2: 'ddd' },
//   { prop1: 444, prop2: 'ddd' }
// ]

There is the same value element on index 4 but it is not removed because it is a different object.

before = array.length;
array.removeByLooseMatch((value) => value.prop1 === 222)
console.log(`length: ${before} -> ${array.length} (removeByLooseMatch)`);
console.log(array);
// length: 8 -> 7 (removeByLooseMatch)
// [
//   { prop1: 111, prop2: 'aaa' },
//   { prop1: 333, prop2: 'ccc' },
//   { prop1: 222, prop2: 'bbb' },
//   { prop1: 444, prop2: 'ddd' },
//   { prop1: 222, prop2: 'bbb' },
//   { prop1: 444, prop2: 'ddd' },
//   { prop1: 444, prop2: 'ddd' }
// ]

This method removes the first occurrence.

before = array.length;
array.removeAllByLooseMatch((value) => value.prop1 === 222);
console.log(`length: ${before} -> ${array.length} (removeAllByLooseMatch)`);
console.log(array);
// length: 7 -> 5 (removeAllByLooseMatch)
// [
//   { prop1: 111, prop2: 'aaa' },
//   { prop1: 333, prop2: 'ccc' },
//   { prop1: 444, prop2: 'ddd' },
//   { prop1: 444, prop2: 'ddd' },
//   { prop1: 444, prop2: 'ddd' }
// ]

Then, remove all the occurrences. Two elements are removed.

before = array.length;
array.removeAllByPerfectMatch({ prop1: 444, prop2: "ddd" });
console.log(`length: ${before} -> ${array.length} (removeAllByPerfectMatch)`);
console.log(array);
// length: 5 -> 5 (removeAllByPerfectMatch)
// [
//   { prop1: 111, prop2: 'aaa' },
//   { prop1: 333, prop2: 'ccc' },
//   { prop1: 444, prop2: 'ddd' },
//   { prop1: 444, prop2: 'ddd' },
//   { prop1: 444, prop2: 'ddd' }
// ]

This call doesn’t remove anything because the passed object is newly created on the call. The same object doesn’t exist in the array.

before = array.length;
array.removeAllByPerfectMatch(obj);
console.log(`length: ${before} -> ${array.length} (removeAllByPerfectMatch)`);
console.log(array);
// length: 5 -> 3 (removeAllByPerfectMatch)
// [
//   { prop1: 111, prop2: 'aaa' },
//   { prop1: 333, prop2: 'ccc' },
//   { prop1: 444, prop2: 'ddd' }
// ]

This method call removes two elements. { prop1: 444, prop2: 'ddd' } still exists in the array because it was added in the initialization. However, the other two elements were added by push(obj) method. Therefore, only the two elements were removed here.

Simple Performance comparison indexOf loop vs for loop

function indexOfLoop(array: number[], deleteValue: number) {
    for (let i = 0; ; i++) {
        const index = array.indexOf(deleteValue);
        if (index < 0) {
            break;
        }
        array.splice(index, 1);
    }
}

function simpleLoop(array: number[], deleteValue: number) {
    for (let i = array.length - 1; i >= 0; i--) {
        if (array[i] === deleteValue) {
            array.splice(i, 1);
        }
    }
}

const count = 1000000;
const array = Array.from(Array(count).keys()).map(x => Math.floor(Math.random() * 100));
const array2 = [...array];

console.time("indexOfLoop");
indexOfLoop(array, 36);
console.timeEnd("indexOfLoop");

console.time("simpleLoop");
simpleLoop(array2, 36);
console.timeEnd("simpleLoop");

I use Node.js 14.13.0. The build command was tsc --lib "es2019,dom".

indexOfLoopsimpleLoop
20.576s4.080s
19.245s3.494s
29.464s3.703s
23.681s5.672s

When I built it with this tscondig, the difference becomes smaller for some reason… Why?

indexOfLoopsimpleLoop
12.226s9.573s
12.985s10.583s
17.819s13.955s
14.653s12.042s

Check this post if you want to remove duplicates.

Comments

Copied title and URL