diff options
Diffstat (limited to 'docs/content/guide/expression.ngdoc')
| -rw-r--r-- | docs/content/guide/expression.ngdoc | 207 | 
1 files changed, 207 insertions, 0 deletions
| diff --git a/docs/content/guide/expression.ngdoc b/docs/content/guide/expression.ngdoc new file mode 100644 index 00000000..421dd9c7 --- /dev/null +++ b/docs/content/guide/expression.ngdoc @@ -0,0 +1,207 @@ +@workInProgress +@ngdoc overview +@name Developer Guide: Expression +@description + +# Expressions +Expressions are the bindings that you write in HTML and embed in templates in order to create +views in angular. They are 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 you want to run arbitrary JavaScript code, 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 are 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. E.g. 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 (e.g. 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 $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: + +<pre> +name | uppercase +</pre> + +The expression evaluator simply passes the value of name to angular.filter.uppercase. + +Chain filters using this syntax: + +<pre> +value | filter1 | filter2 +</pre> + +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 chooses 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 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. + + | 
