Reputation: 345
I have a problem with the following code, and maybe I am just not understanding how filter works. From my understanding filter is supposed to return a new array. The filteredItems is what I am doing the *ngFor on in the DOM. So I first do a shallow copy of my input items array with the slice method into my filteredItems and copyItems. After that I attempt to return a new array of filtered items from the shallow copied items array. However, whenever I try to filter the array of items it actually manipulates the original array's data instead of just returning a new array with the data I need.
@Input() private items: Items[];
private copyItems = new Array<Items>();
public input = new FormControl();
public filteredItems = new Array<Items>();
ngOnInit() {
this.filteredItems = this.items.slice();
this.copyItems = this.items.slice();
this.subscription = this.input.valueChanges
.debounceTime(500)
.distinctUntilChanged()
.map((value) => {
return value;
})
.subscribe((searchTerm) => {
if (searchTerm) {
this.filteredItems = this.filterItems(searchTerm.toLowerCase());
console.log(this.items);
} else {
this.copyItems = this.items.slice();
this.filteredItems = this.items.slice();
}
});
}
private filterItems(searchTerm: string): Array<Items> {
return this.copyItems.filter((item) => {
let filterExists: boolean = false;
let itemName = <string>item.name;
if (itemName.toLowerCase().startsWith(searchTerm)) {
filterExists = true;
}
let filteredChildren = this.filterChildren(searchTerm, item);
if (filteredChildren.length > 0) {
filterExists = true;
item.children = filteredChildren;
}
if (filterExists)
return true;
else
return false;
});
}
private filterChildren(searchTerm: string, item: Items): Array<ChildItems> {
return item.children.filter(child => {
let childName = <string>child.name;
if (childName.toLowerCase().startsWith(searchTerm)) {
return child;
}
});
}
Can someone please tell me what the heck I am doing wrong here. I have been banging my head against my desk reworking this problem over and over for the past two days and cannot figure it out.
Thanks in advance!
Upvotes: 6
Views: 13220
Reputation: 129
No, filter is not creating a new array. In order to be sure to create a new array I would have rather use the reduce function. You can initialize the reduce function with a new array and push new values in it when meeting the right condition.
const filtered = original
.reduce((acc, item) => {
if (item.id % 2 == 1) {
acc.push({id: item.id, state: item.state + 1});
}
return acc;
}, [])
Upvotes: -1
Reputation: 1074148
filter
does indeed create a new array, but the objects referenced in the original are also referenced by the new array, so changes to them are visible through both.
If you're going to change the state of an item in the array and don't want that change visible through the old array, you need to create a new item and use that in the array. That would be map
rather than filter
— or rather, in your case, a combination of the two.
Here's a simpler example of what you're currently doing; note how state
becomes 2
regardless of which array we look it it in:
var original = [
{id: 1, state: 1},
{id: 2, state: 1},
{id: 3, state: 1}
];
var filtered = original.filter(item => {
if (item.id % 2 == 1) {
++item.state;
return item;
}
});
console.log("original", original);
console.log("filtered", filtered);
.as-console-wrapper {
max-height: 100% !important;
}
Sticking to existing Array.prototype
functions, the way you'd do a mutating filter would be to use map
then filter
removing undefined
entries:
var original = [
{id: 1, state: 1},
{id: 2, state: 1},
{id: 3, state: 1}
];
var filtered = original
.map(item => {
if (item.id % 2 == 1) {
return {id: item.id, state: item.state + 1};
}
})
.filter(item => !!item);
console.log("original", original);
console.log("filtered", filtered);
.as-console-wrapper {
max-height: 100% !important;
}
Alternately, you could give yourself a mapFilter
function:
Object.defineProperty(Array.prototype, "mapFilter", {
value: function(callback, thisArg) {
var rv = [];
this.forEach(function(value, index, arr) {
var newValue = callback.call(thisArg, value, index, arr);
if (newValue) {
rv.push(newValue);
}
})
return rv;
}
});
var original = [
{id: 1, state: 1},
{id: 2, state: 1},
{id: 3, state: 1}
];
var filtered = original
.mapFilter(item => {
if (item.id % 2 == 1) {
return {id: item.id, state: item.state + 1};
}
});
console.log("original", original);
console.log("filtered", filtered);
.as-console-wrapper {
max-height: 100% !important;
}
...but all the caveats about extending built-in prototypes apply (you might make it a utility you pass the array to instead).
Upvotes: 11