diff options
| author | Misko Hevery | 2012-02-28 14:29:58 -0800 |
|---|---|---|
| committer | Misko Hevery | 2012-06-02 16:02:08 -0700 |
| commit | 41d26db32c1c013dd33faa03df85e38681a9ebb1 (patch) | |
| tree | a5be9c22e26b239129993fec416a16f33386a8c9 /docs/content/guide/expression.ngdoc | |
| parent | dd38ce6585b0e7ffa755f4c65d78ed90204729d1 (diff) | |
| download | angular.js-41d26db32c1c013dd33faa03df85e38681a9ebb1.tar.bz2 | |
docs(expression): rewrite
Diffstat (limited to 'docs/content/guide/expression.ngdoc')
| -rw-r--r-- | docs/content/guide/expression.ngdoc | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/docs/content/guide/expression.ngdoc b/docs/content/guide/expression.ngdoc new file mode 100644 index 00000000..f92dbe48 --- /dev/null +++ b/docs/content/guide/expression.ngdoc @@ -0,0 +1,187 @@ +@ngdoc overview +@name Developer Guide: Expressions +@description + +Expressions are JavaScript-like code snippets that are usually placed in bindings such as `{{ +expression }}`. Expressions are process by the {@link api/angular.module.ng.$parse $parse} +service. + +For example, these are all valid expressions in angular: + + * `1+2` + * `3*10 | currency` + * `user.name` + + +## Angular Expressions vs. JS Expressions + +It might be tempting to think of angular view expressions as JavaScript expressions, but that is +not entirely correct, since angular does not use a JavaScript `eval()` to evaluate expressions. +You can think of angular expressions as JavaScript expressions with following differences +differences: + + * **Attribute Evaluation:** evaluation of all properties are against the scope, doing the + evaluation, unlike in JavaScript where the expressions are evaluated against the global + `window`. + + * **Forgiving:** expression evaluation is forgiving to undefined and null, unlike in JavaScript, + where such evaluations generate `NullPointerExceptions`. + + * **No Control Flow Statements:** you cannot do any of the following in angular expression: + conditionals, loops, or throw. + + * **Filters:** you can pass result of expression evaluations through filter chains. For example + to convert date object into a local specific human-readable format. + +If, on the other hand, 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 {@link api/angular.module.ng.$rootScope.Scope#$eval `$eval()`} method. + +## Example +<doc:example> +<doc:source> + 1+2={{1+2}} +</doc:source> +<doc:scenario> + it('should calculate expression in binding', function() { + expect(binding('1+2')).toEqual('3'); + }); +</doc:scenario> +</doc:example> + +You can try evaluating different expressions here: + +<doc:example> +<doc:source> + <script> + function Cntl2($scope) { + $scope.exprs = []; + $scope.expr = '3*10|currency'; + $scope.addExp = function(expr) { + this.exprs.push(expr); + }; + + $scope.removeExp = function(index) { + this.exprs.splice(index, 1); + }; + } + </script> + <div ng-controller="Cntl2" class="expressions"> + Expression: + <input type='text' ng-model="expr" size="80"/> + <button ng-click="addExp(expr)">Evaluate</button> + <ul> + <li ng-repeat="expr in exprs"> + [ <a href="" ng-click="removeExp($index)">X</a> ] + <tt>{{expr}}</tt> => <span ng-bind="$parent.$eval(expr)"></span> + </li> + </ul> + </div> +</doc:source> +<doc:scenario> + it('should allow user expression testing', function() { + element('.expressions :button').click(); + var li = using('.expressions ul').repeater('li'); + expect(li.count()).toBe(1); + expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]); + }); +</doc:scenario> +</doc:example> + + +# Property Evaluation + +Evaluation of all properties takes place against a scope. Unlike JavaScript, where names default +to global window properties, angular expressions have to use {@link api/angular.module.ng.$window +`$window`} to refer to the global `window` object. For example, if you want to call `alert()`, which is +defined on `window`, in an expression must use `$window.alert()`. This is done intentionally to +prevent accidental access to the global state (a common source of subtle bugs). + +<doc:example> +<doc:source> + <script> + function Cntl1($window, $scope){ + $scope.name = 'World'; + + $scope.greet = function() { + ($window.mockWindow || $window).alert('Hello ' + this.name); + } + } + </script> + <div class="example2" ng-controller="Cntl1"> + Name: <input ng-model="name" type="text"/> + <button ng-click="greet()">Greet</button> + </div> +</doc:source> +<doc:scenario> + it('should calculate expression in binding', function() { + var alertText; + this.addFutureAction('set mock', function($window, $document, done) { + $window.mockWindow = { + alert: function(text){ alertText = text; } + }; + done(); + }); + element(':button:contains(Greet)').click(); + expect(this.addFuture('alert text', function(done) { + done(null, alertText); + })).toBe('Hello World'); + }); +</doc:scenario> +</doc:example> + +## Forgiving + +Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating `a.b.c` throws +an exception if `a` is not an object. While this makes sense for a general purpose language, the +expression evaluations are primarily used for data binding, which often look like this: + + {{a.b.c}} + +It makes more sense to show nothing than to throw an exception if `a` is undefined (perhaps we are +waiting for the server response, and it will become defined soon). If expression evaluation wasn't +forgiving we'd have to write bindings that clutter the code, for example: `{{((a||{}).b||{}).c}}` + +Similarly, invoking a function `a.b.c()` on undefined or null simply returns undefined. + + +## No Control Flow Statements + +You cannot write a control flow statement in an expression. The reason behind this is core to the +angular philosophy that application logic should be in controllers, not in the view. If you need a +conditional, loop, or to throw from a view expression, delegate to a JavaScript method instead. + + +## Filters + +When presenting data to the user, you might need to convert the data from its raw format to a +user-friendly format. For example, you might have a data object that needs to be formatted +according to the locale before displaying it to the user. You can pass expressions through a chain +of filters like this: + + name | uppercase + +The expression evaluator simply passes the value of name to {@link +api/angular.module.ng.$filter.uppercase `uppercase`} filter. + +Chain filters using this syntax: + + value | filter1 | filter2 + +You can also pass colon-delimited arguments to filters, for example, to display the number 123 +with 2 decimal points: + + 123 | number:2 + +# The $ + +You might be wondering, what is the significance of the $ prefix? It is simply a prefix that +angular uses, to differentiate its API names from others. If angular didn't use $, then evaluating +`a.length()` would return undefined because neither a nor angular define such a property. + +Consider that in a future version of angular we might choose to add a length method, in which case +the behavior of the expression would change. Worse yet, you the developer could create a length +property and then we would have a collision. This problem exists because angular augments existing +objects with additional behavior. By prefixing its additions with $ we are reserving our namespace +so that angular developers and developers who use angular can develop in harmony without collisions. + |
