Angular 2 - Update *ngFor table after data has changed - arrays

Normally I would expect this to update in realtime, but it doesn't seem to be doing so. In my angular app, I have an array that stores a string and another array like so:
var myArray= [{id:"name1",array:{"id1", "id2"}]
And then in my html I have a table that lists every item in myArray in a row
<table *ngFor="item in myArray">
<td>{{item.id}}</td>
<td>{{item.array}}</td>
</table>
The items in the array are added in a function that is bound to a button click. So the users will chose from a list of items, click a button, and those choices get pushed to the array. They are also able to go back and delete some of those items, so I am adding in code to handle the check every item in myArray, and if its array contains that item that was deleted, it will remove it.
for(let i=0; i<myArray.length;i++) {
let row:any = myArray[i];
for(let k=0; k<row.array.length;k++) {
if(row.array[k] == deletedItem) {
row.array.splice(index, 1);
}
}
}
The code works, using console logs I am able to see that the deletedItem is removed from any item in myArray's array (sorry for the confusing variable names!). However, the table doesn't reflect the updated values, even when myArray shows it when submitting or printing to console. Do I have to refresh the table? I thought it should update automatically since the data is bound.

From this code, I think you are not deleting any element from myArray, but from the local variable row. That's why in your template, you don't see any change.
Try with this :
for(let i=0; i<myArray.length;i++) {
let row:any = myArray[i];
for(let k=0; k<row.array.length;k++) {
if(row.array[k] == deletedItem) {
myArray[i].array.splice(k, 1);
}
}
}

I ran into the same problem today and nearly tore my hair out trying to figure it out. Just like you, my console.log looked right but my data wasn't updating even though it was bound. The reason it won't update is that the data source is immutable and the only way to have it update is by assigning the spliced ArrayList to a new array and then assigning it to the data source.
The data I'm going to use is from https://material.angular.io/components/table/overview this is their property, not mine.
EX:
const ELEMENT_DATA: PeriodicElement[] = [
{position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
{position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
{position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
{position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
{position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
{position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
{position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
];
this.dataSource = ELEMENT_DATA;
remove(){
//this is where I splice
this.dataSource.splice(1,1);
//this is where I assign it to the new array.
const ELEMENT_DATAS: PeriodicElements[]=[...this.dataSource];
//this sets the new array to the data source updating it and updating the
table on the UI
this.dataSource = ELEMENT_DATAS;
}

Related

How to generate an array of objects in Alpine.data with Livewire Eloquent collection and Adding properties in those js objects

and thanks for your attention and help,
I have a Collection in my livewire controller. This collection contains a list of players, with some properties : here we will just focus on id and name.
So we can imagine that we have 3 players in the collection :
Players[0] : 'id'=>1, 'name'=>'Yann';
Players[1] : 'id'=>2, 'name'=>'Robert';
Players[2] : 'id'=>3, 'name'=>'Jessica';
I need to get these players in my alpine data.
I can easily get these players in Alpine with the #js method :
window.addEventListener('alpine:init', () => {
Alpine.data('data', () => ({
players: #js($players),
}))
})
So, now I have in my alpine.data :
players: [
{ id: 1, name: 'Yann' },
{ id: 2, name: 'Robert' },
{ id: 3, name: 'Jessica' },
]
Now I can easily display the players in my html with a template x-for :
<template x-for="player in players">
<p x-text="player.name"></p>
</template>
But I want to add some additionnal properties in each player object. Those properties will be updated in front depending user's actions.
I would like to get something like this :
players: [
{ id: 1, name: 'Yann', touched20: 0, touched15: 0 },
{ id: 2, name: 'Robert', touched20: 0, touched15: 0 },
{ id: 3, name: 'Jessica', touched20: 0, touched15: 0 },
]
All additionnal properties are the same for each object in the array, so I imagine i could use a foreach loop to put them in the objects.
But I can't see and don't understand how i can include a loop in my alpine.data script to do this.
Anyone could help me ?
I edit my question because I found a solution :
I just make a loopPlayers function outside of my alpine data and call this function in the alpine data :
function loopPlayers() {
let players = [];
const livewirePlayers = #js($players);
livewirePlayers.forEach(element => {
players.push(element);
});
players.forEach(function(element) {
element.touched15 = 0;
})
return players;
}
And in alpine.data :
players: loopPlayers(),
Now I have my collection of players from my livewire controller & I have new properties for each element of the collection in the js data
That was easy, as usual I guess :)

Adding data to complex state in react reducer

I have a react reducer that manages the following kind of state
const exampleState: INote[][] = [
[
{ x: 3, y: 5, value: '4' },
{ x: 3, y: 6, value: '4' },
],
[
{ x: 7, y: 3, value: '4' },
{ x: 8, y: 5, value: '7' },
{ x: 8, y: 5, value: '6' }
],
];
So a 2D array that contains arrays of a specific type of object. For some reason I can't figure out how to update this kind of state. I want to specifically be able to add a new instance (without mutating original state) of INote to a specific nested array. How can I achieve this? The index of the array I need to add the object to is in my reducers action object
Well, the obvious ways would be to update this state immutably, for instance, let's say I have a ADD_NOTE action, it could look something like that:
{type: "ADD_NOTE", payload: { item: INote, index: number }}
And then, an example return statement of the reducer for this action would be:
return state.map((arr, i) => i === index ? [...arr, item] : arr)
This will update the array, and will add item to the end of the array with the provided action index.
Another solution that might make your life easier is to use help libraries such as https://github.com/kolodny/immutability-helper or https://github.com/immerjs/immer
You need to use an array of objects instead of array of arrays so you can add an identifier such as an id to each object so you can update that specific object based on its id

console logging event target id of an object in an array in an object in react

i am mapping multiple area on an image for a web app in react, i want to target their id's onClick
let targetArea = myMap.find(targetArea => targetArea.id === 10)
let MAP = {
id: 'map', name: 'my-map',
areas: [
{id: 7, name: 'kitchen', shape: 'rect', coords: [804,1001,1019,1135], preFillColor: 'clear'},
{id: 9, name: 'bar pendant', shape: 'rect', coords: [516,1294,732,1428], preFillColor: 'clear'},
{id: 10, name: 'entry', href: 'entry', shape: 'rect', coords: [1034,1292,1246,1428], preFillColor: 'clear'}
]
};
i was getting an array of objects back with console.log(MAP.areas)
but i'm not sure how to dynamically set an id
so far i've got
const myMap = MAP.areas
let targetArea = myMap.find(targetArea => targetArea.id === 10)
areaCheck = (event) => {
console.log(targetArea.name)
}
i'm getting the correct name of the hard set id in my targetArea variable, i feel like i might need a loop idk??
first i passed area as an argument in the onClick event handler
onClick={(area)=> this.areaCheck(area)}
then passed it around my handler
areaCheck = (area) => {
console.log(area.id)
this.setState({ areaClicked: area.id })
}
then it suddenly knew what area i was talking about :)

In an array of obects watched with Vue-watch, how to get index of object that was changed?

I have an array of objects, like this:
myArray: [{
name: "First",
price: 10,
rebate: 5,
listPrice: 15,
outcome: 0
},{
name: "Second",
price: 11,
rebate: 5,
listPrice: 16,
outcome: 0
}
I want to recalculate the outcome-value whenever any of the other values in the same object change.
I already have a setup like this, but it looks for changes in any object and then recalculates the whole array. I've managed to set this up by using a combination of computed and watch functions. However they watch the whole array for changes and then recalculate the outcome-value for all objects in the array.
How can I watch for changes and then recalculate only the changed object?
Below is my current functions for recalculating the whole array (watching another property), but what I'm looking for could be completely different.
computed:
myArrayWasChanged() {
return [this.myArray.reduce((a, {vendors}) => a + vendors, 0), this.myArray.filter(item => item.discounted == false).length]
watch:
myArrayWasChanged: {
handler: function (val, oldVal) {
this.recalculateIsVendor();
Given the outcome is completely dependent on the other properties, it isn't really part of the component's state. Thus, in the component's data you could store the array without the outcome, and then calculate a new version of the array with the outcome as a computed property.
data: function () {
return {
myArrayWithoutOutcome: [
{
name: "First",
price: 10,
rebate: 5,
listPrice: 15
},
{
name: "Second",
price: 11,
rebate: 5,
listPrice: 16
}]
}
},
computed: {
myArrayWithOutcome: function () {
return this.myArrayWithoutOutcome.map(x => {
return {...x, outcome: this.calculateOutcome(x)}
})
}
},
methods: {
calculateOutcome(item) {
// Logic to calculate outcome from item goes here
return 0
}
}

Angular: How to update an array object key value dynamically?

I am trying to change the points of selected object that in the exp below.
$scope.players = [{
name: 'Kobe',
points: 10,
asists: 0,
rebounds: 0
}, {
name: 'Jordan',
points: 20,
asists: 0,
rebounds: 0
}, {
name: 'Grant',
points: 30,
asists: 0,
rebounds: 0
},
];
and I assign an object selected with its name.
if($scope.playerName == $scope.players[i].name){
$scope.selectedPlayerPoints = $scope.players[i].points;
$scope.selectedPlayerAsists = $scope.players[i].asists;
$scope.selectedPlayerRebounds = $scope.players[i].rebounds;
}
but I can't update them:
$scope.selectedPlayerPoints.push(playerPoints);
To make it more clear please check: http://plnkr.co/edit/B8Nydni586Se79fDpjnq?p=preview
How it works:
1-click on a player
2-click on points = each time 2 points will be added.
3-as you add more point, it will change the object dynamically..(but that is the problem..)
Thnx in advance!
I'm not exactly sure what you want to achieve, but my guess is you have trouble with saving player points.
I've updated your plunkr. Basically instead of passing primitive values:
$scope.selectPlayer = function(name, points, asists, rebounds) { ... }
you should pass object reference:
$scope.selectPlayer = function(player) { ... }

Resources