Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use underscore.js "pluck" on a knockout observable array

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!

like image 257
Lon Kastenson Avatar asked May 05 '15 16:05

Lon Kastenson


1 Answers

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

like image 197
JotaBe Avatar answered Sep 18 '22 03:09

JotaBe