diff options
Diffstat (limited to 'docs/content/guide/dev_guide.expressions.ngdoc')
| -rw-r--r-- | docs/content/guide/dev_guide.expressions.ngdoc | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/docs/content/guide/dev_guide.expressions.ngdoc b/docs/content/guide/dev_guide.expressions.ngdoc new file mode 100644 index 00000000..b0a268e6 --- /dev/null +++ b/docs/content/guide/dev_guide.expressions.ngdoc @@ -0,0 +1,270 @@ +@workInProgress +@ngdoc overview +@name Developer Guide: Understanding Angular Expressions +@description + + +Expressions are {@link dev_guide.templates.databinding bindings} that you write in HTML and embed +in templates in order to create views in angular. Angular expressions are similar but not +equivalent to JavaScript expressions. + + +For example, these are all valid expressions in angular: + + +* `1+2={{1+2}}` +* `3*10|currency` +* `Hello {{name}}!` +* `Hello {{'World'}}!` + + + + +## Angular Expressions vs. JS Expressions + + +It might be tempting to think of angular view expressions as JavaScript expressions, but that is +not entirely correct. Angular does not use a simple JavaScript eval of the expression text. You can +think of angular expressions as JavaScript expressions with these differences: + + +* **Attribute Evaluation:** evaluation of all attributes are against the current scope, not to the +global window as in JavaScript. +* **Forgiving:** expression evaluation is forgiving to undefined and null, unlike in JavaScript. +* **No Control Flow Statements:** you cannot do the following from an angular expression: +conditionals, loops, or throw. +* **Type Augmentation:** the scope expression evaluator augments built-in types. +* **Filters:** you can add filters to an expression, for example to convert raw data into a +human-readable format. +* **The $:** angular reserves this prefix to differentiate its API names from others. + + +If, on the other hand, you do want to run arbitrary JavaScript code, you should make it a +controller method and call that. If you want to `eval()` an angular expression from JavaScript, use +the `Scope:$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> + <div ng:init="exprs=[]" class="expressions"> + Expression: + <input type='text' name="expr" value="3*10|currency" size="80"/> + <button ng:click="exprs.$add(expr)">Evaluate</button> + <ul> + <li ng:repeat="expr in exprs"> + [ <a href="" ng:click="exprs.$remove(expr)">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> + + + + +# Attribute Evaluation + + +Evaluation of all attributes takes place against the current scope. Unlike JavaScript, where names +default to global window properties, angular expressions have to use `$window` to refer to the +global object. For example, if you want to call `alert()`, which is defined on `window`, 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> + <div class="example2" ng:init="$window = $service('$window')"> + Name: <input name="name" type="text" value="World"/> + <button ng:click="($window.mockWindow || $window).alert('Hello ' + name)">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. + + +Assignments work the same way in reverse: + + + a.b.c = 10 + + +...creates the intermediary objects even if a is 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 (including ternary operators), loop, or to throw from a view expression, delegate to a +JavaScript method instead. + + + + +## Type Augmentation + + +Built-in types have methods like `[].push()`, but the richness of these methods is limited. +Consider the example below, which allows you to do a simple search over a canned set of contacts. +The example would be much more complicated if we did not have the `Array:$filter()`. There is no +built-in method on `Array` called {@link api/angular.array.filter $filter} and angular doesn't add +it to `Array.prototype` because that could collide with other JavaScript frameworks. + + +For this reason the scope expression evaluator augments the built-in types to make them act like +they have extra methods. The actual method for `$filter()` is `angular.Array.filter()`. You can +call it from JavaScript. + + +Extensions: You can further extend the expression vocabulary by adding new methods to +`angular.Array` or `angular.String`, etc. + + +<doc:example> +<doc:source> + <div ng:init="friends = [ + {name:'John', phone:'555-1212'}, + {name:'Mary', phone:'555-9876'}, + {name:'Mike', phone:'555-4321'}, + {name:'Adam', phone:'555-5678'}, + {name:'Julie', phone:'555-8765'}]"></div> + Search: <input name="searchText"/> + <table class="example3"> + <tr><th>Name</th><th>Phone</th><tr> + <tr ng:repeat="friend in friends.$filter(searchText)"> + <td>{{friend.name}}</td> + <td>{{friend.phone}}</td> + </tr> + </table> +</doc:source> +<doc:scenario> + it('should filter the list', function(){ + var tr = using('table.example3').repeater('tr.ng-attr-widget'); + expect(tr.count()).toBe(5); + input('searchText').enter('a'); + expect(tr.count()).toBe(2); + + + }); +</doc:scenario> +</doc:example> + + +## 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 angular.filter.uppercase. + + +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. + + + + +## Related Topics + + +* {@link dev_guide.compiler.markup Understanding Angular Markup} +* {@link dev_guide.templates.filters Understanding Angular Filters} + + +## Related API + + +* {@link api/angular.compile Angular Compiler API} |
