AngularJS $parse hacks

A few useful applications of AngularJS parsing service.

There is a great little function hiding in AngularJs: $parse. Typically it is used internally to interpolate values, for example in two way binding:

1
2
<p>Hello, {{ user.name }}</p>
<!-- where user is an object in the scope -->

You can see this simple example in action at http://jsbin.com/pitoy/1.

Angularjs will call $parse to evaluate the expression user.name, placing the result into the DOM.

$parse evaluates expressions, not just property access, for example you can achieve the same result with

1
<p>{{ 'Hello ' + user.name }}</p>

You can see this yourself at http://jsbin.com/pitoy/2.

You can use $parse in your application code by injecting it into your controller (or any other function). The parse operates in two steps: first it compiles your expression into a template function. Then you call the template function with a context and local variables. Your context and locals are usually the $scope object:

1
2
3
4
5
6
7
function controller($scope, $parse) {
$scope.user = {
name: 'Joe'
};
var template = $parse('Hello + user.name');
var msg = template($scope); // Hello Joe
}

The 2-step execution is similar to the way other templating libraries like Handlebars work.

$parse is really forgiving, for example if the $scope.user object does not exist, the expression still evaluates fine, but returns an undefined which is placed as empty string, see at http://jsbin.com/pitoy/3. This leads to my first $parse hack.

Hack 1: safe deep property access using $parse

If we have an object with properties that might be null, grabbing a nested value requires lots of guard conditions

1
2
3
4
5
6
7
8
9
10
11
12
13
var foo = {
bar: {
baz: {
name: 'baz'
}
}
};
var name;
if (typeof foo === 'object' &&
typeof foo.bar === 'object' &&
typeof foo.bar.baz === 'object') {
name = foo.bar.baz.name;
}

Of course you could use a helper library like l33teral and wrap objects for safer access

1
2
3
var leet = require('l33teral');
var fooL = leet(foo);
var name = fooL.tap('bar.baz.name');

But if you are already inside AngularJs application, just use $parse

1
var name = $parse('bar.baz.name')(foo);

See entire example at http://jsbin.com/pitoy/4. If a property does not exist, the call returns undefined

1
$parse('bar.baz2.name')(foo); // returns undefined

You can even store the first function to avoid recompiling the expression every time

1
2
3
var getName = $parse('bar.baz.name');
...
getName(foo);

Hack 2: send logic from backend to the client

If you need to dynamically compute something on the client, you can send the logic from the server as a string. You can even split low level methods and local variables when evaluating the parsed string by using $parse with 2 arguments (context and locals)

1
2
3
4
5
6
7
8
9
10
var ops = {
add: function (a, b) { return a + b; },
mul: function (a, b) { return a * b; }
};
var logic = 'mul(add(a, 1), b)';
var data = {
a: 10,
b: 4
};
var result = $parse(logic)(ops, data); // 44

See live code at http://jsbin.com/pitoy/5.

data argument can override properties in the context argument ops, but I suggest keeping methods separate from data for cleaner implementation.

Hack 3: implement spreadsheet in 20 minutes

When demoing the AngularJs power, I like using David Graunke's spreadsheet example. This is super impressive example that uses $parse to dynamically evaluate expressions inside each cell. The expressions might refer to values of other cells, which can refer to other cells, etc. The relevant logic is in placing all cells onto the scope and creating a compute function that runs any time a user changes a cell's value

1
2
3
4
5
6
7
8
function sheetController($scope, $parse) {
$scope.columns = ['A', 'B', 'C', 'D'];
$scope.rows = [1, 2, 3, 4];
$scope.cells = {}; // will be filled with row x column objects
$scope.compute = function(cell) {
return $parse($scope.cells[cell])($scope);
};
}

You can see the spreadsheet in action here. Try entering any number in cell A1, then enter A1 + A1 in cell B1. As soon as you remove focus from B1 it will show the computed value (which should be double the value of A1).