Pesquisa de site

javascript - filter não está criando um novo array?


Eu tenho um problema com o seguinte código, e talvez eu apenas não esteja entendendo como o filter funciona. Pelo meu entendimento, o filter é suposto retornar um novo array. O filteredItems é o que eu estou usando no *ngFor no DOM. Então, primeiro eu faço uma cópia superficial do meu array de itens de entrada com o método slice para o meu filteredItems e copyItems. Depois disso, eu tento retornar um novo array de itens filtrados a partir do array de itens copiado superficialmente. No entanto, sempre que tento filtrar o array de itens, ele na verdade manipula os dados do array original em vez de apenas retornar um novo array com os dados que eu preciso.

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

Alguém pode, por favor, me dizer o que diabos estou fazendo de errado aqui. Tenho batido minha cabeça contra a mesa, refazendo este problema várias vezes nos últimos dois dias e não consigo descobrir.

Obrigado(a) desde já!

Resposta #1

filter de fato cria um novo array, mas os objetos referenciados no original também são referenciados pelo novo array, portanto alterações neles são visíveis em ambos.

Se você vai alterar o estado de um item no array e não quer que essa alteração seja visível através do array antigo, você precisa criar um novo item e usar ele no array. Isso seria map em vez de filter — ou melhor, no seu caso, uma combinação dos dois.

Aqui está um exemplo mais simples do que você está fazendo atualmente; observe como state se torna 2 independentemente de qual array nós olhamos:

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

Seguindo as funções existentes de Array.prototype, a maneira de fazer um filtro que modifica o array seria usar map e depois filter removendo entradas undefined:

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

Alternativamente, você poderia criar uma função mapFilter:

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

...mas todas as ressalvas sobre estender protótipos embutidos se aplicam (você pode transformá-lo em uma utilidade para a qual você passa o array, em vez disso).

Artigos relacionados