Reputation: 1885
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
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