How to not delete last item from my array - arrays

I have a problem with deleting an item from an array. My current piece of code is removing the item. But it it always the last one not the one that I choose. I have found some clues that index needs to be checked against -1
Array
ngOnInit() {
this.todos = [
{
text: 'Pick up'
},
{
text: 'Meeting'
},
{
text: 'Dish washing'
}
];
}
Functions
addTodo(){
this.todos.push({
text: this.text
});
}
deleteTodo(todoText){
this.todos.splice(this.todos.indexOf(todoText), 1);
}

Do not pass only the todoText to your deletion method, pass the whole todo, so if your template looks like this:
<div *ngFor="let todo of todos" (click)="deleteTodo(todo)">
{{todo.text}}
</div>
and then your delete method works fine in your TS:
deleteTodo(todo){
this.todos.splice(this.todos.indexOf(todo), 1);
}

the problem in your code is that you are compare by object reference, so do this :
deleteTodo(todoText){
this.todos
.splice(this.todos.map(todo=>todo.text)
.indexOf(todoText.text), 1);
}
I'm assuming that todoText argument is an object that has text property.
if you want to delete by text:
deleteTodo(text){
this.todos
.splice(this.todos.map(todo=>todo.text)
.indexOf(text), 1);
}

Related

Remove item from state

I know this question has been asked a lot but I haven't seem to find a solution even tho I've tried different scenarios.
this.state = {
chartTypes: [{name: 'Bar', id: 1},{name: 'Line', id: 2},{name: 'Pie', id: 3},{name: 'Radar', id: 4} ],
selectedChart: [],
}
onSelect = (selectedList, selectedItem) => {
// selectedItem.name returns name of chartTypes, selectedItem.id returns id of chartTypes.
this.setState({
selectedChart: selectedItem.name
})
}
onRemove = (selectedList, removedItem) => {
// removedItem.name returns name of chartTypes, removedItem.id returns id of chartTypes.
}
The select option works fine but I just put it there so you can have a better understanding. onSelect puts every selectedItem.name into selectedChart. On the remove function, how may I remove item from this.state.selectedChart based on the value/index.
I think you can do something like this
let temp = this.state.selectedChart;
temp=temp.filter((item) => item !== removedItem.name);
this.setState({ selectedChart: temp });
var newArray = this.state.selectedChart.filter((el)=>
el.id !==removedItem.id
);
after filter
this.setState({selectedChart:newArray})
Just filter and set backed to state
onRemove = (selectedList, removedItem) => {
let filtered = this.state.chartTypes.filter(list=> list.id !==removedItem.id);
this.setState({ chartTypes: filtered });
}

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>
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.

Protractor: How to find an element in an ng-repeat by text?

I'm looking to get a specific element inside an ng-repeat in protractor by the text of one of its properties (index subject to change).
HTML
<div ng-repeat="item in items">
<span class="item-name">
{{item.name}}
</span>
<span class="item-other">
{{item.other}}
</span>
</div>
I understand that if I knew the index I wanted, say 2, I could just do:
element.all(by.repeater('item in items')).get(2).element(by.css('.item-name'));
But in this specific case I'm looking for the 'item in items' that has the specific text (item.name) of say "apple". As mentioned, the index will be different each time. Any thoughts on how to go about this?
public items = element.all(by.binding('item.name'))
getItemByName(expectedName) {
return this.items.filter((currentItem) => {
return currentItem.getText().then((currentItemText) => {
return expectedName === currentItemText;
});
}).first();
}
And invoke method like that this.getItemByName('Item 1'). Replace Item 1 with expected string.
function elementThere(specificText, boolShouldBeThere){
var isThere = '';
element.all(by.repeater('item in items')).each(function (theElement, index) {
theElement.getText().then(function (text) {
// Uncomment the next line to test the function
//console.log(text + ' ?= ' + specificText);
if(text.indexOf(specificText) != -1){
element.all(by.repeater('item in items')).get(index).click();
isThere = isThere.concat('|');
}
});
});
browser.driver.sleep(0).then(function () {
expect(isThere.indexOf('|') != -1).toBe(boolShouldBeThere);
});
}
it('should contain the desired text', function () {
elementThere('apple', true);
}
Does this fit your needs?
I was able to solve this by simplifying #bdf7kt's proposed solution:
element.all(by.repeater('item in items')).each(function(elem) {
elem.getText().then(function(text) {
if(text.indexOf('apple') != -1) {
//do something with elem
}
});
});
Also, this particular solution doesn't work for my use case, but I'm sure will work for others:
var item = element(by.cssContainingText('.item-name', 'apple'));
//do something with item

How to remap array only on changes?

I have component Page which contains components Editor and Preview.
Page contains array items.
[
{
value: 0,
text: 'Item 1'
},
...
]
Array items is passed to Editor & Preview like this:
<editor [items]="items"></editor>
<preview [items]="items"></preview>
Editor can add/delete/edit/reorder items.
Issue is preview needs this array in another format.
[
{
index: 0,
label: 'Item 1'
},
...
]
If I do like this
getRadioItems(): any[] {
const items = [];
for (let i = 0; i < this.items.length; i++) {
items.push({ index: this.items[i].value,
label: this.items[i].text });
}
return items;
}
and then
<radio-list [radioItems]="getRadioItems()"></radio-list>
It refreshes radio list hundreds times per second. You can't even change value because it will be reset on every refresh.
If it were without remapping - it would work fine.
What is correct way to remap items to radioItems in such case?
Have you tried setting the ChangeDetectionStrategy of the preview component to OnPush? Then change detection should only be run when the #Input() items is updated.
It is stupid solution, but it works.
getRadioItems(): any[] {
const newJson = JSON.stringify(this.items);
if (this.json === newJson) {
return this.cachedItems;
}
this.json = newJson;
this.cachedItems = [];
for (let i = 0; i < this.items.length; i++) {
this.cachedItems.push({ index: this.items[i].value,
label: this.items[i].text });
}
return this.cachedItems;
}

ng-class - finding a value inside object

I have an object that looks like this:
$scope.things = [
{
name: 'Bob!',
short_name: 'bob',
info: 'something something'
},
{
name: 'Steve',
short_name: 'steve',
info: 'something something something'
},
];
I loop through them like this and add an ng-click:
<div ng-repeat="thing in things" ng-click="addThing(thing.name, thing.short_name, thing_info" ng-class="thingClass(thing.name)">content goes here</div>
the ng-click="addThing()" basically bunches up the values and adds them to the object.
When clicked, it should add the class selected - this worked fine and dandy when I wasn't using a multidimensional object, because it was simply looking for name inside the object / array (at this point, I think it's an object... but at the time, it was an array)
I can't work out how to do the equivalent to this...
$scope.thingClass= function(name) {
if($scope.thingSelected.indexOf(name) != -1) {
return 'selected';
}
};
...with the object as it now stands. I've tried to adapt a few answers from here that I found through google, such as:
$scope.teamClass = function(name) {
var found = $filter('filter')($scope.thingSelected, {id: name}, true);
if (found.length) {
return 'selected';
}
};
...but with no joy.
Can anyone point / nudge me in the right direction?
You could simply pass the thing object to thingClass:
... ng-class="thingClass(thing)" ...
and implement thingClass as follows:
$scope.thingClass= function(thing) {
return $scope.thingSelected.indexOf(thing) >= 0 ? 'selected' : '';
}
And maybe your should apply this technique to addThing also:
... ng-click="addThing(thing)" ...
$scope.addThing = function(thing) {
if ($scope.thingSelected.indexOf(thing) < 0)
$scope.thingSelected.push(thing);
}
But instead of tracking the selected things in an array its much easier to introduce a selected property in each thing:
$scope.addThing = function(thing) {
thing.selected = true;
}
$scope.thingClass= function(thing) {
return thing.selected ? 'selected' : '';
}

Resources