I have an observable array of objects and I want to pluck out the values using underscore.js
For example:
ko.observableArray([{
id: ko.observable(1),
name: ko.observable("name1")
},
{
id: ko.observable(2),
name: ko.observable("name2")
},
...])
And I just want to pluck the values inside of the object rather than the whole observable.
Can I do this with just one command?
I tried:
_.pluck(myArray(), "id()") and _.pluck(myArray(), "id"())
But these return an array of undefineds and "id is not a function" respectively.
Thanks!
Short answer
Use _.invoke instead of _.pluck
See this sample fiddle.
Long answer
_.pluck(list, propertyName) works as documented:
A convenient version of what is perhaps the most common use-case for map: extracting a list of property values.
Or, as better exlained on lodash docs: _.pluck(collection, path)
Gets the property value of path from all elements in collection.
So, if you do this:
_.pluck(myArray(), "id")
what you get is an array with all the id's. And all of these id's are observables, as in the objects of the original array
But you can use _.invoke(list, methodName, *arguments), which, as documented:
Calls the method named by methodName on each value in the list. Any extra arguments passed to invoke will be forwarded on to the method invocation.
or, on lodash version _.invoke(collection, path, [args])
Invokes the method at path on each element in collection, returning an array of the results of each invoked method. Any additional arguments are provided to each invoked method. If methodName is a function it is invoked for, and this bound to, each element in collection.
In this way, you execute each observable, and get its value as expected:
_.invoke(myArray(), "id")
Mind the viewmodels full of observables!
The first comment to this question has made me include this notice:
The best solution is using ko.toJS to convert all the observables in a view model into a regular JavaScript object, with regular properties. Once you do it, underscore, or any other library, will work as expected.
The _.invoke solution only works for a single level of observables, as this case. If there were several level of nested observables, it will completely fail, because it invokes a function at the end of the path, not at each step of the path, for example, _.invoke wouldn't work for this case:
var advices = [{
person: ko.observable({
name = ko.observable('John')
}),
advice: ko.observable('Beware of the observables!')
}];
In this case, you could only use _.invoke on the first level, like this:
var sentences = _.invoke(advices,'advice');
But this wouldn't work:
var names = _.invoke(advices,'person.name');
In this call, only name would be invoked, but person not, so this would fail, because person is an observable, thus it doesn't have a name property.
NOTE: lodash is another library similar, and mostly compatible with underscore, but better in some aspects
I was able to solve this by using the "map" function:
_.map(myArray(), function(item) {return item.id()});
But I was hoping to use pluck since it's the exact use-case for this type of scenario.
Because name is a function, how about pluck your original array into an array of functions, then using ko.toJS to convert it into string array?
var myArray = ko.observableArray([{
id: ko.observable(1),
name: ko.observable("name1")
},
{
id: ko.observable(2),
name: ko.observable("name2")
}]);
var names = _.pluck(myArray(), 'name');
console.log(ko.toJS(names)); // Output: ["name1", "name2"]
Unwrap it first
_.pluck(ko.toJS(myArray), 'id')
_(ko.toJS(myArray)).pluck('id)
Related
I have an array of objects that is fed by an external API e.g
[{prop: val1}, {prop: val2}, {prop: val3}....]
I have to feed this object(my code) to a third-party library which expects the name of the property on the object to be 'xyz' instead of 'prop'.
What is the most efficient way (memory wise and faster) basically avoiding both:
1. iterating over the array
2. adding 'xyz' property to all objects in the array
to achieve this?
I am thinking along the lines of adding a getter for xyz to all objects that return the 'prop' value, but that does not save the looping.
Adding the getting on the prototype level (Object.property) seems like a bad idea at this point.
Edit: I am not looking for different ways to loop through arrays in javascript like forEach or map. I have a very specific ask, and i am interested in exploring if it is at all possible to simply have a property proxy for 'xyz'.
Array map is used to cycle trough an array.
myArray.map(function(obj){
obj.xyz = 'yourvalue';
return obj;
}
You can use Array.map to create a new from the array you received by the API.
var newArray = oldArray.map(function(obj){
return {newKey : obj.prop};
});
In this example, newKey will be the key property you want, instead of 'prop', and it's assigned the old 'prop' value
I have a lodash variable;
var usernames = _.map(data, 'usernames');
which produces the following;
[
"joebloggs",
"joebloggs",
"simongarfunkel",
"chrispine",
"billgates",
"billgates"
]
How could I adjust the lodash statement so that it only returns an array of unique values? e.g.
var username = _.map(data, 'usernames').uniq();
Many ways, but uniq() isn't a method on array, it's a lodash method.
_.uniq(_.map(data, 'usernames'))
Or:
_.chain(data).map('usernames').uniq().value()
(The second is untested and possibly wrong, but it's close.)
As mentioned in the comment, depending on what your data actually is, you could do this all in one shot without first pulling out whatever usernames is.
You can also use uniqBy function which also accepts iteratee invoked for each element in the array. It accepts two arguments as below, where id is the iteratee parameter.
_.uniqBy(array, 'id')
I would like to use lodash's _.includes method in my code, but any time I have an array of objects I can't get it to work, and instead end up relying on the _.find method.
From my tests I can only get _.includes to work with simply arrays. But maybe that's the way it's supposed to work?
I am very new to Lodash and programming in general, so I thought I would ask in case I am missing something about how I can use this method.
I created a jsbin with the following code: http://jsbin.com/regojupiro/2/
var myArray = [];
function createArray(attName, attId, otherId) {
var theObject = {};
theObject.attributeName = attName;
theObject.attributeId = attId;
theObject.otherId = [otherId];
return theObject;
}
myArray.push(createArray('first', 1001, 301));
myArray.push(createArray('second', 1002, 302));
myArray.push(createArray('third', 1003, 303));
myArray.push(createArray('fourth', 1004, 304));
var isPresent1 = _.includes(myArray, {'attribtueId' : 1001});
var isPresent2 = _.includes(myArray, 1001);
var found = _.find(myArray, {'attributeId' : 1001});
console.log(isPresent1);
console.log(isPresent2);
console.log(found);
console.log(myArray);
Both "isPresent" variables return false, but the _.find method returns the correct object.
I would love some help in better understanding how I could use the _.includes method when I just need to do a simple true/false check to see if a value is present in my array of objects.
Or, if this is the wrong tool for the job, is the _.find method the right tool for this job, or some other lodash method that I'm not familiar with yet?
Thank you for your help!
I think some() does exactly what you're looking for.
The _.includes() method compares with the SameValueZero comparator, which is a special comparison mostly like ===. Even if you have an object in your array that looks like {'attribtueId' : 1001} that _.includes() call will never find it because two distinct objects will never compare as === to each other.
When you pass an object to _.find(), by contrast, the library assumes that you want it to carry out an _.matches() comparison, which will compare properties of the "target" object. Thus, in your case, _.find() is probably the right choice. The _.includes method really fills a distinct niche.
I like the implementation to be as generic and functional (as in functional programming) as possible, but generally speaking, i'm expecting a json objected with the following structure:
[
{
id: number,
prop1: string,
prop2: number,
prop3: string,
...,
propN: string
},
...
]
(basically, an array of object that contain N properties, some mapped to strings and others to numbers)
I am trying to implement a generic set of functions so that i'll be able to achieve something to this end:
var filteredResult = filter(by(property, value, lt\gt\eq\contains), collection);
basically, I'd like to return an array with the same object structure, filtered by a property string that I pass into by(), along with the value (either a string or a number) and the type of comparison i'd like to perform.
generally speaking, for numbers I'd like to be able to filter results where property values are greater/lessthan/in range of the value I pass, with with strings, or arrays of strings, I'd like to find out if the property value contains the value I pass into by().
Since I'm new to FP, i'm struggling with formatting my code to take advantage of the auto-currying Ramda provides and I'm having trouble composing the different functions while passing the arguments I want.
For example, I've written this function:
var byProperty = function(p) {return R.useWith(R.filter, R.propEq(p), R.identity)};
but when I try to use it like so:
var property = 'prop1', value = 15;
console.log( byProperty( property, value, collection ) );
I get a function instead of the filtered array.
I know I'm missing something trivial here, but it's been kind of hard for me to wrap my head around the way values and functions are passed around in Ramda.
but when I try to use it like console.log( byProperty( property, value, collection ) ) I get a function instead of the filtered array.
Yes, because your function takes only a single parameter, and returns a function. You could invoke it like this:
console.log( byProperty(property)(value, collection) );
but that's probably not what you want. Also, I think useWith is the wrong tool here, you just want a compose:
var byProperty = R.compose(R.filter, R.propEq);
though that still would need to be called like
console.log( byProperty(property, value)(collection) );
There is an open issue for this, but currying and composing variadic functions is not trivial. The best you'll get is probably
var byProperty = R.curryN(3, function(p, v, c) { return R.filter(R.propEq(p, v), c); });
I send a model to a template. The model has a collection. In the template I echo some variables and functions:
console.log(comments);
console.log(_.size(comments));
console.log(comments instanceof App.Collections.Comments);
console.log(_.pluck(comments, 'created'));
_.each(comments, function(com) {
console.log(com);
});
The first three work, but the last two underscore functions don't. Pluck gives 3x undefined and each doesn't iterate.
Object { length=3, models=[3], _byId={...}, more...}
3
true
[undefined, undefined, undefined]
How do I get the underscore functions to work?
Backbone collections have some Underscore methods mixed in so you can use the Underscore methods directly on the collection instance:
console.log(comments.pluck('created'));
comments.each(function(com) { console.log(com) });
Demo: http://jsfiddle.net/ambiguous/3jRNX/
This one:
console.log(_.size(comments));
works fine for you because _.size looks like this:
_.size = function(obj) {
return _.toArray(obj).length;
};
and _.toArray calls the collection's toArray:
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
which unwraps the collection's data to give you the correct length. The above is taken from the 1.3.1 source, the current Github master version's _.size has a different implementation so your _.size call is likely to break during an upgrade.
You'll want to call pluck directly on the collection, as the Collection class supports it:
http://documentcloud.github.com/backbone/#Collection-pluck
So instead of:
_.pluck(comments, 'created')
You chould call:
comments.pluck('created');