Angular2 - Why does initializing my array cause problems with my data structure? - javascript

I have a component that is subscribed to some data used to populate a table. This table uses *ngFor to loop over the array of data and output it to the page, typical stuff.
When I define my array like so importResults: ImportResults[];, my data appears to get stored as intended and I am left with an array of objects.
ngOnInit() {
// Subscribe to our subject that lets us know about the added employees
this._massEmpService.importedData.subscribe(obj => {
if (obj) {
obj.checked = false;
this.importResults = obj;
}
});
}
With this setup, I can use *ngFor without issues by simply doing:
<tbody>
<tr *ngFor="let i of importResults" >
<td>
<input type="checkbox"
id="checkbox_{{ i.QID }}"
[checked]="i.checked"
(click)="toggleSelectedEmployee(i)"
[(ngModel)]="i.checked" />
</td>
...
</tr>
</tbody>
However... when I initialize my array like so importResults: ImportResults[] = []; it alters my data structure.
This leaves me with an array of arrays of objects?
This causes me to have to do some weird nested looping on my table which doesn't seem right.
<tbody *ngFor="let res of importResults">
<tr *ngFor="let i of res" >
<td>
<input type="checkbox"
id="checkbox_{{ i.QID }}"
[checked]="i.checked"
(click)="toggleSelectedEmployee(i)"
[(ngModel)]="i.checked" />
</td>
...
</tr>
</tbody>
Is this expected behavior to need to nest stuff like this? The first way works fine for how I would expect to be able to loop but because its not initialized, I can't push new data to it which is why I had to go with the expected way of defining it Array[] = [];
Am I doing something wrong here?

In your first instance, you declare the variable, but don't initialize it, then when you subscribe to the data service you assign the resulting array of objects to the variable:
this.importResults = obj;
In your second case, you declare the variable and initialize it as an empty array:
importResults: ImportResults[] = [];
Then when you get your data back from the service you're doing this:
this.importResults.push(obj);.
This is taking the returned array of objects from the data service and pushing it into the array you've already created, so that's why it ends up being nested. In the first, you're making your variable equal to the array of objects you're getting back, and in the second you're pushing the array of objects into the existing array.
If you used the same assignment in the second case:
this.importResults = obj;
You wouldn't have this problem, whether you initialized the variable to an empty array when you declared it or not.
If what you're trying to do is fetch one or more objects from a service and add them to an existing array, which may already have one or more objects inside, then you want to return the objects via subscribe, and iterate over them, pushing each object in that returned array one at a time into the existing array.

Related

Passing associative array as props not working

I have a React application which handles rooms and their statistics.
Previously, I had the code set up to pass as props to the next component:
the raw statistics (not a concern for the question)
an array of all the rooms set up as follows
I figured it would be simpler for me, though, to have the list of all rooms as an associative array where the keys of each element is the same as the ID it contains. To do that, I utilized a code similar to this in a for loop:
roomsList[rooms[v].ID] = rooms[v];
So that the result would be:
[a001: {...}, a002: {...}, ...]
I then proceeded to pass this style of array, and not the standard one with a numeric index, as a prop to the next component as such:
<StatsBreakdown stats={computedStats.current} roomsList={roomsList} />
BUT
Now, the next component sees that prop as an empty array.
Even more weirdly, if I initialize that roomsList array with a random value [0] and then do the same process, I end up with:
I cannot cycle through the array with .map, and, according to JS, the length is actually 0, it's not only Google Chrome.
Is there something I'm missing about the way JSX, JS or React work?
Your original roomsList was an array of objects, whose indices were 0,1,2 etc. roomsList[rooms[v].ID] = rooms[v]; implies you are inserting elements not using a number but an alphanumeric string. Hence your resulting array is no longer an array but an object.
So we can cycle over the object using Object.keys().
const renderRoomDets = Object.keys(roomsList).map(room => {
roomOwner = roomsList[room].owner_id;
return (
<div>
<p>{`Room Owner ${roomOwner}`}</p>
</div>
);
});
But I believe your original form is ideal, because you are reaping no special benefits from this notation.
A better alternative maybe using .find() or .findIndex() if you want iterate over an array based on a specific property.
const matchedRoom = roomsList.find(room => room.ID === 'Srf4323')
Iterate the new array using its keys not indexes.
Or even better store your data in an actual object instead of an array since you're using strings for ids.
First define your object like so:
let data = {};
Then start adding records to it. I'd suggest deleting the ID attribute of the object since you're storing it in the key of your record, it's redundant, and it won't go anywhere unless u delete the entry.
data[ID] = row;
To delete the ID attribute (optional):
row.ID = null;
delete row.ID;
Then iterate through it using
for(let key in data){}

Update Object $scope item by $parent.$index and $index

What Im Attempting to Do
Im pushing an object into an array, this works as expected. But as I try to update one of the objects in the array via their $parent.$index and $index all objects are updated.
Object Being Pushed Into Array (Multiple Times)
// Array for Objects
$scope.arr = []
// Object to be pushed into Array
$scope.obj = {
content:[
{
type:"text",
data:"This is Dummy Text",
style:{
"height":"500px"
}
},
// Could be more than one Object within Content
]
}
The above object will be pushed into $scope.arr multiple times, within the view the objects are looped.
// Looped Arrays
<div ng-repeat="l1 in arr track by $index">
<div ng-repeat="l2 in l1.content" ng-style="l1.style">{{l1.data}}</div>
</div>
Updating by $parent.$index and $index
So at this point I have pushed the $scope.obj multiple times into $scope.arr and this is where the issue occurs.
I need to update only one of the $scope.obj's in the $scope.arr via a line of the code like the following:
// Set $index's to target the specific array items
var parentIndex = 0
var index = 0
$scope.arr[parentIndex].content[index].style['height']
An example of a possible update would be the following:
var o = parseInt($scope.arr[parentIndex].content[index].style['height'])
var n = o + 1
$scope.arr[parentIndex].content[index].style['height'] = new + 'px'
At the moment the above will update all inserted/pushed objects in $scope.arr despite setting the correct $parent.$index and $index. Where as I need to target and update one, not all.
I must be missing something here, any help or guidance is greatly appreciated.
When pushing, try to do a copy of the object like so:
$scope.arr.push(angular.copy($scope.obj));
Since you keep the important parts of your code secret (how you insert the "objects" into the "array") I can only guess, that you're "inserting" the same object to multiple places (means: you keep the reference to the same object in multiple indexes in the array, so basically you have only 1 object) and then when you change the object in "1 place" using array[1].object.a=2, then you'll see the change in "each" index: array[4].object.a==2, because they refer to the same object actually

ng-repeat with different nested json

So I have an API that returns a list of activities,but I have to get the info 2 items at a time so if I have 3 items on the first call the json returned would be something like this:
cesta{
fecha="14/08/2015 2:42:28 PM",
tipo="9",
pagina="1",
total="3",
inst [{
nu_doc_opened="N",
nu_doc="14",
nu_inst="1",
nb_wf{
cdata_section="Gestión calendario específico"
}
},
nu_doc_opened="N",
nu_doc="15",
nu_inst="2",
nb_wf{
cdata_section="Gestión calendario general"
}
}]
}
and on the next call the json returned would be like this
cesta{
fecha="14/08/2015 2:42:29 PM",
tipo="9",
pagina="2",
total="3",
inst {
nu_doc_opened="N",
nu_doc="16",
nu_inst="1",
nb_wf{
cdata_section="Gestión calendario específico"
}
}
}
I want to go throgh this json and get some of the data and put it in a table,so I'm trying to use ng-repeat like this: (Note that I only want to get some of the values not all)
$scope.basket= data.cesta.inst;
<tbody>
<tr ng-repeat="b in basket">
<td>{{b.nu_doc}}</td>
<td>{{b.nu_inst}}</td>
<td>{{b.nb_wf.cdata_section}}</td>
</tr>
</tbody>
The problem is that this works when the json has 2 or more objects in 'inst' (like in the first example) because it's an array, but when it has only 1 object won't work, What could I do so the ng-repeat work in both cases?
Thanks in advanced
This is because in second case it get object an when iterate the object the it gets the properties and values not the objects like in first array.
Wrap that object in array then it works.
$scope.basket= angular.isArray(data.cesta.inst) ? data.cesta.inst : [data.cesta.inst];
As you've realized, ng-repeat expects an array. If you can't force the endpoint to return an array always (even when there's only one data object), then you may have to write a hacky workaround.
One hacky idea:
$scope.basket = data.cest.inst.length !== undefined ? data.cest.inst : [data.cest.inst]
I think you get this, but in case not, here's the reason why $scope.basket needs to be an array:
In short: ng-repeat expects an array. In the first case, $scope.basket is an array of objects, but in the second, it's just an object. In the second, you need to place the object inside of an array (i.e. [{nu_doc: ...}]) so that the format is consistent. (Note: It's ok to have an array with only one object!)
For example, in the first case, you get:
$scope.basket = [{nu_doc: ...}, {nu_doc: ...}]
And that works fine with the ng-repeat statement as you've written it.
But in the second case, you get:
$scope.basket = {nu_doc: ...}
And so the ng-repeat will loop through the properties on that object (i.e. nu_doc, nu_inst ...) instead of treating it as an array with a single object, which is what you want.
So your second piece of JSON needs to be:
cesta{
fecha="14/08/2015 2:42:29 PM",
tipo="9",
pagina="2",
total="3",
inst [{
nu_doc_opened="N",
nu_doc="16",
nu_inst="1",
nb_wf{
cdata_section="Gestión calendario específico"
}
}
}]

Looping through json object from a checkbox loop

I have a json object thats being loaded through a checkbox in an ng-repeat. It lists possible services and I'm trying to get access to the ones that have been checked when I submit the form.
I'm loading the checkbox through:
<label style="margin-right:10px" ng-repeat="item in dsServces">
<input
type="checkbox"
name="selectedFruits"
value="{{item.DefaultServiceID}}"
ng-model="expense.dsService[item.DefaultServiceName]"
> {{item.DefaultServiceName}}
</label>
I can see the expense is being built by testing with:
{{ expense.dsService }}
returns (when a couple are selected)
{"Email":true,"Backups":true}
But I can't work out how to loop through so it says just the service name so I can add them against the customer.
In the back end I have:
$scope.expense = {
tags: []
};
And on the submit:
angular.forEach($scope.expense, function(item) {
console.log(item);
});
Which spits out Object {Email: true, Backups: true} but I can't work out to have a loop where it just returns which ever objects are true.
My main aim is to have something like
angular.forEach(forloop) {
//service.item should be like 'Email' whatever the item.DefaultServiceName is
// the loop should be all items that are checked
addService(service.item);
});
If I am understanding properly, you want to push the key values of the object into an array. If that is the case, just make a little change to your forEach()
Like so:
// The first parameter is what you want to loop through
// Second is the iterator, you can separate out the key and values here
// The third is the context, since I put the array we want to push to in here
// we just need to call `this.push()` within the iterator
angular.forEach($scope.expense, function(value, key) {
this.push(key);
}, $scope.expense.tags);
Let me know if this helps.

Passing an associative array to jTemplates as a parameter

I've created a jTemplate to display an array of "test" objects. The array is a regular indexed array. The template is very basic, just uses a {#foreach} to iterate through the items in the array and displays them in a small table. This template dos the job and I get the expected output.
// Setup the JTemplate.
$('#tests_div').setTemplate($('#tests_template').html());
try {
// Process the JTemplate to display all currently selected tests.
$('#tests_div').processTemplate(_selectedTests);
}
catch (e) {
alert('Error with processing template: ' + e.Description);
}
<script type="text/html" id="tests_template">
{#foreach $T as tests}
<table>
<tr>
<td>Index: {$T.tests.index}</td>
<td>Name: {$T.tests.firstname} {$T.tests.lastname}</td>
<td>Score: {$T.tests.score} </td>
</tr>
</table>
{#/for}
</script>
What I'd like to do is change my array to be an associative array and store my objects in it using the test's index. This makes it easier to work with when I need to do some manipulation of the tests later on.
var a = new Test;
a.index = 12345678;
_selectedTests[a.index] = a;
However, when I pass the array to the template, I get an error about the script is causing my browswer to run slow, asking if I would like to stop it. It seems like its in some kind of endless loop. I'm not sure the template is reading the array correctly. Can anyone tell me how I work with the associative array inside the jTemplates?
You issue is that your array thinks it is huge:
_selectedTests[12345678] = a; // creates an array of 12345678 elements!! length of 12345678
so you can do this:
_selectedTests[a.index.toString()] = a; // creates an associative array with one key "12345678", length of 1

Categories

Resources