154

I was curious about the $scope.$eval you so often see in directives, so I checked out the source and found the following in rootScope.js:

  $eval: function(expr, locals) {
    return $parse(expr)(this, locals);
  },

$parse appears to be defined by ParseProvider in parse.js, which appears to define some kind of mini-syntax of its own (the file is 900 lines long).

My questions are:

  1. What exactly is $eval doing? Why does it need its own mini parsing language?

  2. Why isn't plain old JavaScript eval being used?

sigod
  • 3,514
  • 2
  • 21
  • 44
Jonah
  • 15,806
  • 22
  • 87
  • 161

3 Answers3

188

$eval and $parse don't evaluate JavaScript; they evaluate AngularJS expressions. The linked documentation explains the differences between expressions and JavaScript.

Q: What exactly is $eval doing? Why does it need its own mini parsing language?

From the docs:

Expressions are JavaScript-like code snippets that are usually placed in bindings such as {{ expression }}. Expressions are processed by $parse service.

It's a JavaScript-like mini-language that limits what you can run (e.g. no control flow statements, excepting the ternary operator) as well as adds some AngularJS goodness (e.g. filters).

Q: Why isn't plain old javascript "eval" being used?

Because it's not actually evaluating JavaScript. As the docs say:

If ... you do want to run arbitrary JavaScript code, you should make it a controller method and call the method. If you want to eval() an angular expression from JavaScript, use the $eval() method.

The docs linked to above have a lot more information.

Josh David Miller
  • 120,525
  • 16
  • 127
  • 95
  • You said $eval not actually evaluating JavaScript but if I do $eval("{id: 'val'}") I get a JS object. (Angular 1.0.8) – Gabriel Oct 15 '13 at 09:54
  • 7
    @Yappli `$eval` doesn't evaluate JavaScript; it evaluates AngularJS expressions, which are kind of like a safer subset of JavaScript. `"{id: 'val'}"` is a valid AngularJS expression and should return a valid JS object. See the link above for difference between expressions and regular JS. – Josh David Miller Oct 15 '13 at 17:00
  • 1
    Nice answer but I'm not sure that "e.g. no control flow statements" is accurate. You can do something like this... ng-click="someVal ? someFunc(someVal) : noop" which is a perfectly valid angular expression – Charlie Martin Aug 08 '14 at 16:58
  • @CharlieMartin The documentation specifically says ["no control flow statements"](https://docs.angularjs.org/guide/expression), but your point is valid. However, I definitely wouldn't recommend doing that in an ngClick; that kind of logic almost certainly belongs in the controller. – Josh David Miller Aug 08 '14 at 19:50
  • 1
    Interesting that the docs say that as the support for the ternary operator was intentionally added... https://github.com/angular/angular.js/blob/master/src/ng/parse.js#L587-601 The ng-click was just a simple example and I agree that logic should be in a controller, but I don't see anything wrong with using a ternary operator in the bracket notation and the angular team obviously doesn't either or they wouldn't have added support for it. I suppose if a correction were to be made, it should happen in the docs before this answer though – Charlie Martin Aug 11 '14 at 16:17
  • I'm not arguing with the usefulness of the ternary operator *generally*, but just the wisdom of using it in the specific case of API control flow with `ng-click`. But anyway, the ternary operator was added somewhat recently and when I wrote this answer (and probably when the docs were last upated) it was not supported and there were *truly* no control flow statements supported. Either way, I updated the answer. :-) – Josh David Miller Aug 11 '14 at 19:23
  • What you're talking about is called expression sandbox and will be removed on Angular 1.6: https://angularjs.blogspot.com.ar/2016/09/angular-16-expression-sandbox-removal.html – Alejandro García Iglesias Nov 30 '16 at 12:29
22

From the test,

it('should allow passing locals to the expression', inject(function($rootScope) {
  expect($rootScope.$eval('a+1', {a: 2})).toBe(3);

  $rootScope.$eval(function(scope, locals) {
    scope.c = locals.b + 4;
  }, {b: 3});
  expect($rootScope.c).toBe(7);
}));

We also can pass locals for evaluation expression.

allenhwkim
  • 27,270
  • 18
  • 89
  • 122
2

I think one of the original questions here was not answered. I believe that vanilla eval() is not used because then angular apps would not work as Chrome apps, which explicitly prevent eval() from being used for security reasons.

Kevin MacDonald
  • 650
  • 8
  • 21