IndexOf indicates that an object doesn't exist, even though it should - javascript

I'm trying to get the index of an item in a database query, but for some reason I'm getting -1 when trying to use indexOf on the data.
The data is displayed in a md-select that repeats over a collection of objects (roles):
<div layout="row">
<md-input-container>
<label>Job Activity:</label>
<md-select ng-model="activity" required>
<md-option ng-repeat="activity in activities" ng-value="activity">
{{ activity.Description }}
</md-option>
</md-select>
</md-input-container>
</div>
Displaying works absolutely fine, but when I submit the form that the above md-select lives in, I simply cannot find the index of the selected activity in the collection of activities / roles, even though it COMES from that collection and no manipulation occurs.
Here is the submit that occurs when the form has been submitted. Role is the role that has been selected from the md-select:
vm.submitEmployment = function (role) {
console.log(role);
console.log($scope.activities.indexOf($scope.role));
console.log($scope.activities);
$scope.data.role = role.Id;
$scope.data.roleIndex = $scope.activities.indexOf($scope.role);
$window.location.href = '#!/additionalInformation';
}
The above console.logs give me this:
Now I'm not rocket scientist, but those two objects look exactly the same for me. Is there something I'm missing?

Alter your method slightly, using the Array.findIndex method to get what you want. The lodash _.findIndex() achieves the same thing, if you use lodash:
vm.submitEmployment = function (role) {
// the idx variable should hold your index number
var idx = $scope.activities.findIndex((item) => $scope.role.Id === item.Id);
}

try to map your array to get IDs then search the index of your Id :
$scope.activities.map(function(role) {
return role.Id;
}).indexOf(role.Id);

indexOf() compares searchElement to elements of the Array using strict
equality (the same method used by the === or triple-equals operator)
var a = {a: 2}
var b = {a: 2}
a === b // false
The reason for this is that internally JavaScript actually has two different approaches for testing equality. Primitives like strings and numbers are compared by their value, while objects like arrays, dates, and plain objects are compared by their reference. That comparison by reference basically checks to see if the objects given refer to the same location in memory.
So, if you are searching by id try something like:
$scope.activities.map(function(a) { return a.id; }).indexOf($scope.role.id)

If you're fine with using ES6, you can use the findIndex function. So, you can do something like this:
const index = $scope.activities.findIndex(item => item.id === role.id);

Related

Grab an object out of objects base on specific field value

In my AngularJS
this.device return 15 objects. Each object contain a dataType field and it has diff values on all 15 of them. I would like to grab/access only if dataType == "PROTOCOL" I would do something like this if I have access to underscore.js, but in this project, I don't have it.
this.device.protocol = _.find(this.device, {dataType: "PROTOCOL"});
What is JS way to access this without having to do a for-loop?
You can use Array#find.
The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.
this.device.protocol = this.device.find(x => x.dataType === "PROTOCOL");
You can use Array.filter and check against your desired property.
this.device.protocol = this.device.filter(({dataType}) => dataType == "PROTOCOL")[0];

Prevent pushing to array if duplicate values are present

I'm mapping an array and based on data i'm pushing Option elements into an array as follows
let make_children: any | null | undefined = [];
buyerActivityResult && buyerActivityResult.simulcastMyAccount.data.map((item: { make: {} | null | undefined; }, key: any) => {
make_children.push(
<Option key={key}>{item.make}</Option>
);
});
Following data array has several objects and these objects have an attribute called model.
buyerActivityResult.simulcastMyAccount.data
I want to prevent pusing Options to my array if the attribute model has duplicate data. It only has to push once for all similar model values.
How can i do it?
I tried something like this
buyerActivityResult && buyerActivityResult.simulcastMyAccount.data.map((item: { model: {} | null | undefined; }, key: any) => {
model_children.indexOf(item.model) === -1 && model_children.push(
<Option key={key}>{item.model}</Option>
);
});
But still duplicate values are being pushed into my array.
Its difficult to tell what you are trying to achieve but it looks like a map may not be the right tool for the job.
A map returns the same sized length array as that of the original array that you are calling map on.
If my assumptions are correct, your buyerActivityResult.simulcastMyAccount.data array has duplicate values, and you want to remove these duplicates based on the model property? One way to achieve this would be to use the lodash library for this, using the uniq function:
const uniqueResults = _.uniq(buyerActivityResult.simulcastMyAccount.data, (item) => item.model);
The Array.prototype.map() method is supposed to be used for manipulating the data contained into the array performing the operation. To manipulate data from other variables I recommend to use a for-loop block.
If item.model is an object, the function Array.prototype.indexOf() always returns -1 because it compares the memory address of the objects and does not do a deep comparison of all properties values.
The usual solution to remove duplicate data from an array is converting the Array into a Set then back to an Array. Unfortunately, this works only on primary type values (string, number, boolean, etc...) and not on objects.
Starting here, I will review your source code and do some changes and explain why I would apply those changes. First of all, assuming the make_children array does not receive new attribution later in your code, I would turn it into a constant. Because of the initialization, I think the declaration is overtyped.
const make_children: any[] = [];
Then I think you try to do too much things at the same time. It makes reading of the source code difficult for your colleagues, for you too (maybe not today but what about in few weeks...) and it make testing, debugging and improvements nearly impossible. Let's break it down in at least 2 steps. First one is transforming the data. For example remove duplicate. And the second one create the Option element base on the result of the previous operation.
const data: { make: any }[] = buyerActivityResult?.simulcastMyAccount?.data || [];
let options = data.map((item) => !!item.model); // removing items without model.
// Here the hard part, removing duplicates.
// - if the models inside your items have a property with unique value (like an ID) you can implement a function to do so yourself. Take a look at: https://stackoverflow.com/questions/2218999/remove-duplicates-from-an-array-of-objects-in-javascript
// - or you can use Lodash library like suggested Rezaa91 in its answer
options = _.uniq(data, (item) => item.model);
Now you only have to create the Option elements.
for (var i = 0; i < options.length; i++) {
model_children.push(<Option key={i}>{options[i].model}</Option>);
}
// OR using the Array.prototype.map method (in this case, do not declare `model_children` at the beginning)
const model_children:[] = options.map((opt:any, i:number) => <Option key={i}>{opt.model}</Option>);
Despite the lack of context of the execution of the code you provided I hope my answer will help you to find a solution and encourage you to write clearer source code (for the sake of your colleagues and your future self).
PS: I do not know anything about ReactJs. forgive me my syntax mistakes.

how to access key/value pairs from json() object?

I'm calling an external service and I get the returned domain object like this:
var domainObject = responseObject.json();
This converts the response object into a js object. I can then easily access a property on this object like this
var users = domainObject.Users
Users is a collection of key/value pairs like this:
1: "Bob Smith"
2: "Jane Doe"
3: "Bill Jones"
But CDT shows users as Object type and users[0] returns undefined. So how can I get a handle to the first item in the collection? I'm assuming that some type of type cast is needed but not sure how I should go about doing this
UPDATE
Here is one way I could access the values:
//get first user key
Object.keys(responseObject.json().Users)[0]
//get first user value
Object.values(responseObject.json().Users)[0]
But I need to databind through ng2 so I was hoping for a simpler way like this:
<div>
<div *ngFor="let user of users">
User Name: {{user.value}}
<br>
</div>
</div>
Maybe I should just create a conversion function in my ng2 component which converts the object into what I need before setting the databinding variable?
UPDATED ANSWER
So after scouring through a few docs I found the "newish" Object.entries() javascript function. You can read about it here. Pretty cool.
Anyways, give this a try. I am ashamed to say that I don't have time to test it, but it should get you going in the right direction.
usersArray = []
// Turn Users object into array of [key, value] sub arrays.
userPairs = Object.entries(users);
// Add the users back into an array in the original order.
for (i=0; i < userPairs; i++) {
usersArray.push(_.find(userPairs, function(userPair) { return userPair[0] == i }))
}
ORIGINAL ANSWER
I would use either underscore.js or lodash to do this. Both are super helpful libraries in terms of dealing with data structures and keeping code to a minimum. I would personally use the _.values function in lodash. Read more about it here.. Then you could use users[0] to retrieve the first item.
The only caveat to this is that lodash doesn't guarantee the iteration sequence will be the same as it is when the object is passed in.
users = _.values(users);
console.log(users[0]);
How about this:
let user= this.users.find(() => true)
This should return the "first" one.
If your initial object is just a plain object, how do you know it is sorted. Property members are not sorted, ie: looping order is nor guaranteed. I´d extract the user names into an array and the sort that array by the second word. This should work (as long as surnames are the second word, and only single spaces are used as separators).
var l=[];
for(var x in users) {
push.l(users[x]);
}
var l1=l.sort ( (a,b) => return a.split(" ")[1]<b.split(" ")[1]);

How to delete object from array if value matched from other object?

I have already existed array of object values now when i delete dataItem dataItem has same properties that i have in selectedOwners so if dataItem selected value matched i want to delete that object from selectedOwners array.
How can i achieve that task using AngularJs or Javascript ?
ctrl.js
var selectedOwners = [{"fullName":"Johnson, Rocio","workerKey":3506},{"fullName":"Johnson, John S.","workerKey":571}];
$scope.deleteOwner = function(dataItem){
angular.forEach(selectedOwners,function(val,index){
if(val === dataItem){
selectedOwners.splice(index,1);
}
})
}
Unfortunately in Javascript you don't have many instrument for a good equality checking, and === isn't enough, === don't force javascript to convert the two operand in order to perform the equality check on the same type of object, we let say that in this way you have true if the two objects have the same memory reference false otherwise.
For this reason you should decide the your equality criteria and wrap this login in a function. I discourage to use something like this Object.prototype.equals because in the sway you will have the same behavior for all objects in your script
the rest of code that you had post is good in my opinion but you have implements a equality checking
I hope that this can help you

Using indexOf to remove an Object from a list?

So, I have seen this piece of code:
removeOrder = (order) ->
index = listOfOrders.indexOf(order)
if index isnt -1
listOfOrders.splice index, 1
where order is an object like this:
order = {
id: whatever
field1: whatever
...
}
This is working now because the order that is passed as argument is referencing some object in listOfOrders, something like removeOrder(listOfOrders[i]). But my question is, is this safe? I mean, I think it would be better to iterate over the list of orders and search for an object with the same id, for example, and remove it.
As far as I know indexOf is Ok when the object we are searching for is a "simple" object (a number, a string, etc.).
According to These docs for indexOf, indexOf uses strict equality ===.
var a = {id:1};
var b = {id:1};
a === a; // this is true
a === b; // this is false
So its safe to in general for objects
You do need to check that indexOf != -1 before the splice though
var removeOrder = function(order)
{
var index = listOfOrders.indexOf(order);
if (index != -1)
return listOfOrders.splice(index, 1); //for chaining?
return listOfOrders;
}
It's safe to use indexOf if the intended purpose is to remove an object from the array by it's reference.
If you want to remove first object by id let's say, you would use something like:
var removeOrderById = function(orderId)
{
var orders = listOfOrders.filter(function(item)
{
return item.id == orderId;
});
if (orders.length > 0)
{
var index = listOfOrders.indexOf(orders[0]);
return listOfOrders.splice(index, 1); //for chaining?
}
return listOfOrders;
}
Conclusion: it's all about the use case.
The function works as inteded assuming that there is exactly one reference to the object in the array.
If the object isn't in the array (or you are calling it with a clone of an object in the array), the index will be -1 and the splice call will remove the last item in the array instead. As this is not a reasonable behaviour, there should really be a check for that (which I see that you added).
The indexOf method works reliably for object references, but it natually has to be looking for the same object.
Looking for an object with a specific propery value would also work, but it goes with the same assumptions; the value has to occur exactly once in the array to work as intended.

Categories

Resources