Cleaner AngularJS Directives With Curried Functions

A first step to move your function definitions out of the return block.

I really love working with AngularJS. This is why it makes me sad that lots of AngularJS code out there is hardly readable spaghetti code, especially when it comes to custom directives.

We’ve all been there…

app.directive('nameChanger', function() {
return {
restrict: 'E',
templateUrl: 'template.html',
scope: {
model: '=model'
},
link: function(scope, element, attrs, ctrl) {
      // this is where spaghetti code will evolve
scope.setName = function(name) {
scope.model.name = name;
}
    }
};
});

see Plunker example


The problem is the setName function was defined within the link function. In this example the directive still looks readable, but as the directive becomes more complex, there will be more and longer methods on the scope.
Soon your link function body will have a couple hundred lines of code.


Your mission:
Get your function definitions out of the return block!

Say Hello to Partial Application!

There are different approaches to define a function outside of the return block of the directive. One of them is to use curried methods.

Currying methods is a well known pattern in functional programming and also applicable to JavaScript. I think the documentation of the Lo-Dash library has a good explanation for their _.curry method:

Creates a function which accepts one or more arguments of func that when invoked either executes func returning its result, if all func arguments have been provided, or returns a function that accepts one or more of the remaining func arguments, and so on.

Or, as a code example…

var curried = _.curry(function(a, b, c) {
console.log(a + b + c);
});

curried(1)(2)(3);
// → 6

curried(1, 2)(3);
// → 6

curried(1, 2, 3);
// → 6

The Lo-Dash _.curry method provides a some nice additional features, but since ECMAScript 5 you can do this simply using Function.prototype.bind.

function concat(a,b,c){ return [a,b,c].join('&'); }
concat('one','two','three');
// → "one&two&three"
var prefilled = concat.bind(null, 'four', 'five');
// → returns function wrapper
prefilled('six')
// → "four&five&six"
prefilled('seven')
// → "four&five&seven"

Currying your directive functions

Let’s take the directive from above and modify it to use currying.

app.directive('nameChanger', function() {
  function setName(scope, name) {
scope.model.name = name;
}
  return {
restrict: 'E',
templateUrl: 'template.html',
scope: {
model: '=model'
},
link: function(scope, element, attrs, ctrl) {
      // no spaghetti code here :)
scope.setName = setName.bind(null, scope);
    }
};
});

see Plunker example

As you can see, the setName function is now outside the return block.
It now takes two arguments (as it needs to know about the scope).

We could use a wrapper function for scope.setName to pass the scope like this:

scope.setName = function(name) { 
setName(scope, name)
};

But instead we use our curried method to “prefill” the first argument once in the link function and leave the passing of the second parameter name to the template:

scope.setName = setName.bind(null, scope);

This way, we have all the information we need in the setName function, outside of the return block and only need one line of code in the link function to make the setName function accessible from the template.

Note that setName.bind(null, scope) only returns a wrapper function and does not execute the setName function immediately. This will be done in the template, because now these have the same effect:

scope.setName("Bob");
// results in the same as
setName.bind(null, scope)("Bob");
// results in the same as
setName(scope, "Bob");

It’s not a solution applicable to every directive, but I think it it’s an option to be considered, when you try to keep your directives code readable and clean.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.