dmuensterer
dmuensterer

Reputation: 1885

Change elements from Array1 to Array2 and vice versa

Looks so simple, yet I don't know how to solve this efficiently.

I have two arrays activeClasses and doneClasses that each contain JavaScript Objects as their elements.

Each element should be able to be marked as "active" or "done" and should be deleted from the current, and added to the other array if its status changes after clicking "Save". How can I achieve this without mixing up my array indices? Behaviour is as expected except when selecting multiple elements:

https://stackblitz.com/edit/angular-etzocz?file=src%2Fapp%2Fapp.component.html

TS

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {

  activeChanged:Array<boolean> = [];
  doneChanged:Array<boolean> = [];

  toggleActive(i) {
    this.activeChanged[i] = !this.activeChanged[i];
    console.log('activeChanged:');
    console.log(this.activeChanged);
  }

  toggleDone(i) {
    this.doneChanged[i] = !this.doneChanged[i];
    console.log('doneChanged:');
    console.log(this.doneChanged);
  }

  save() {

    var activeToBeDeleted:Array<number> = [];
    var doneToBeDeleted:Array<number> = [];

    //Check if active classes have changed
    this.activeChanged.forEach(function (elem, index) {
      //Has changed
      if (elem) {
        this.doneClasses.push(this.activeClasses[index]);
        //Add to activeToBeDeleted
        activeToBeDeleted.push(index)
      }
    }.bind(this))
    //Check if done classes have changed
    this.doneChanged.forEach(function (elem, index) {
      //Has changed
      if (elem) {
        this.activeClasses.push(this.doneClasses[index]);
        //Add to doneToBeDeleted
        doneToBeDeleted.push(index)
      }
    }.bind(this))

    console.log('before deletion')
    console.log(this.activeClasses)
    console.log(this.doneClasses)

    //Delete array elements that were changed
    activeToBeDeleted.forEach(function(elem) {
      this.activeClasses.splice(elem,1)
    }.bind(this))

    doneToBeDeleted.forEach(function(elem) {
      this.doneClasses.splice(elem,1);
    }.bind(this))

    console.log('after deletion')
    console.log(this.activeClasses)
    console.log(this.doneClasses)

    //Rewrite activeChanged and doneChanged arrays again with false
    this.activeChanged = new Array(this.activeClasses.length).fill(false)
    this.doneChanged = new Array(this.doneClasses.length).fill(false)



  }


  //As from database
  activeClasses:Array<Object> = [
    {
     name: 'test1'
    },
    {
      name: 'test2'
    }
  ];


  doneClasses:Array<Object> = [
    {
    name: 'test3'
    },
    {
      name: 'test4'
    }
  ];

  ngOnInit() {
    //Fill activeChanged and doneChanged with false by default
    this.activeChanged = new Array(this.activeClasses.length).fill(false)
    this.doneChanged = new Array(this.doneClasses.length).fill(false)
  }

}

HTML

<div *ngFor="let active_class of activeClasses; let i = index" style="background-color: blue; text-align: center; padding: 20px; color: white;">
  <button *ngIf="!activeChanged[i]" (click)="toggleActive(i)">Mark as done</button>
  <button *ngIf="activeChanged[i]" (click)="toggleActive(i)">Mark as active</button>
  {{ active_class.name }}
</div>
<div *ngFor="let done_class of doneClasses; let i = index" style="background-color: red; text-align: center; padding: 20px; color: white;">
  <button *ngIf="!doneChanged[i]" (click)="toggleDone(i)">Mark as active</button>
  <button *ngIf="doneChanged[i]" (click)="toggleDone(i)">Mark as done</button>
  {{ done_class.name }}
</div>
<button (click)="save()">Save</button>

Upvotes: 0

Views: 115

Answers (1)

Max
Max

Reputation: 1069

It's because when you splice the items in natural sort order, the array indexes change for the items after the first one you remove.

The solution is to do call reverse() before splicing, which allows you to progress down the array without impacting indexes.

This fixes it:

//Delete array elements that were changed
activeToBeDeleted.reverse().forEach(function(elem) {
   this.activeClasses.splice(elem,1)
}.bind(this))

doneToBeDeleted.reverse().forEach(function(elem) {
  this.doneClasses.splice(elem,1);
}.bind(this))

Why is it working?

First, activeChanged and doneChanged are arrays storing booleans at the index of the item modified (active, or done, see toggle methods). When you first loop over these arrays in the Save method, it loops over the items in ascending order, and thus, you are storing the indexes in ascending order into the activeToBeDeleted and doneToBeDeleted arrays.

So, after that, when you loop over the activeToBeDeleted and doneToBeDeleted arrays and delete from the activeClasses or doneClasses, then while the first delete works, none of the other deletes can work, because the first delete action removed an item from the beginning of the array, and caused all following indexes to be shifted and incorrect.

The solution works because by reversing the list of indexes (going in descending order), you are deleting from the end of the arrays working towards the beginning, which naturally preserves all the indexes. I'd recommend you use pen and pencil, it's a classic pattern actually.

Upvotes: 1

Related Questions