I'm kind of newbie on JS, but I've done my research on the subject and found no exhaustive answer.
I'm using Angular JS for a project, and I have a service that expose some functions:
var app = angular.module('myApp', [])
app.service('mySrv', function() {
this.myFoo = function() {
var anObject = {}
//some code on the object...
return anObject
}
this.myGoo = function() { // function(object) maybe?
//some code on an object again
return aNewObject
}
}
Then in my controller I want to do something like JQuery does, chaining the output of myFoo as the input of myGoo
$scope.myObj = mySrv.myFoo(['some','params']).myGoo('another_one')
I've found that I could use the prototype properties in order to add chainability, as stated here How to make chainable function in JavaScript? But the same answer says that it should not be done on Objects, which will be my case...
Last but not least, ther is this other method JavaScript Object Method Chaining: useful?
which, frankly, I didn't understand...
Can someone clear all this fog from my mind?
What the hell that part exactly does?
var o = {
setTest: function (test) {
this.test = test;
return this
},
getTest: function () {
this.test = this.test.toUpperCase()
return this
},
splitMe: function() {
return this.test.split('')
}
};
console.log( o.setTest('hello').getTest().splitMe() )
Here's an example on how you could make any object methods chainable dynamically.
Calling chainable(obj)
will internally create a new object that exposes the same API as obj
, however every function will now return that new object which in turns allow chaining. If you want to call a function and get the raw result of that function, you can call endChain()
.
The following implementation is not very efficient since it has to loop over all members of obj
. A better approach would be to make your methods chainable by returning the current instance (this
) from the functions you want to be chainable.
var chainable = (function () {
function wrapFn(fn, obj) {
return function () {
var result = this._result = fn.apply(obj, arguments);
return this;
};
}
function Chainable(obj) {
var k, v;
for (k in obj) {
if (typeof (v = obj[k]) !== 'function') continue;
this[k] = wrapFn(obj[k], obj);
}
}
Chainable.prototype.endChain = function () {
return this._result;
};
return function (obj) {
return new Chainable(obj);
};
})();
var o = {
setTest: function (test) {
this.test = test;
},
getTest: function () {
return this.test;
}
};
console.log(chainable(o).setTest('test').getTest().endChain());
Here's a different implementation that doesn't require to iterate over the object members. However it's less appealing syntaxically and I am not sure that it's more efficient since it has to slice
arguments.
var chainable = (function (slice) {
var Chainable = {
init: function (obj) {
this._obj = obj;
return this;
},
chain: function (fn) {
var obj = this._obj;
this._result = obj[fn].apply(obj, slice.call(arguments, 1));
return this;
},
end: function () {
return this._result;
}
};
return function (obj) {
return Object.create(Chainable).init(obj);
};
})(Array.prototype.slice);
var o = {
setTest: function (test) {
this.test = test;
},
getTest: function () {
return this.test;
}
};
console.log(chainable(o).chain('setTest', 'test').chain('getTest').end());
EDIT:
Using and mixing the great examples from @plalx and @Connor, I've managed to get this snippet working, and as you can see, is missing the "chainable part" of @plalx example.
You do not need something like chainable
if your code is already written in a way it can be chained (e.g. already returning this
from some functions). I should have made it more clear. I just wrote the chainable
implementation as an example on how chaining works.
However, with chainable
you can make any object chainable, even if none of their functions explicitely return this
. It could be useful if you want to use chaining with objects that aren't supporting it through their API.
E.g.
var o = {
fn1: function () { console.log('fn1'); },
fn2: function () { console.log('fn2'); }
};
//chainable(o).fn1().fn2(); //other implementation
chainable(o).chain('fn1').chain('fn2');
Here's a performance test that shows making your object dynamically chainable has a huge negative performance impact. For that reason I wouldn't recommend using that experimental approach.