2011-06-06 15:50:35 +00:00
|
|
|
@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>
|
2011-10-07 18:27:49 +00:00
|
|
|
it('should calculate expression in binding', function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
expect(binding('1+2')).toEqual('3');
|
|
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
|
|
|
|
|
You can try evaluating different expressions here:
|
|
|
|
|
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
2011-09-08 20:56:29 +00:00
|
|
|
<script>
|
2011-10-07 18:27:49 +00:00
|
|
|
function Cntl2() {
|
2011-09-08 20:56:29 +00:00
|
|
|
this.exprs = [];
|
|
|
|
|
this.expr = '3*10|currency';
|
2011-11-09 01:40:52 +00:00
|
|
|
this.addExp = function(expr) {
|
|
|
|
|
this.exprs.push(expr);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.removeExp = function(contact) {
|
|
|
|
|
for ( var i = 0, ii = this.exprs.length; i < ii; i++) {
|
|
|
|
|
if (contact === this.exprs[i]) {
|
|
|
|
|
this.exprs.splice(i, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2011-09-08 20:56:29 +00:00
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
<div ng:controller="Cntl2" class="expressions">
|
2011-06-06 15:50:35 +00:00
|
|
|
Expression:
|
2011-09-08 20:56:29 +00:00
|
|
|
<input type='text' ng:model="expr" size="80"/>
|
2011-11-09 01:40:52 +00:00
|
|
|
<button ng:click="addExp(expr)">Evaluate</button>
|
2011-06-06 15:50:35 +00:00
|
|
|
<ul>
|
|
|
|
|
<li ng:repeat="expr in exprs">
|
2011-11-09 01:40:52 +00:00
|
|
|
[ <a href="" ng:click="removeExp(expr)">X</a> ]
|
2011-06-06 15:50:35 +00:00
|
|
|
<tt>{{expr}}</tt> => <span ng:bind="$parent.$eval(expr)"></span>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
2011-10-07 18:27:49 +00:00
|
|
|
it('should allow user expression testing', function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
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>
|
2011-09-08 20:56:29 +00:00
|
|
|
<script>
|
|
|
|
|
function Cntl1($window){
|
|
|
|
|
this.name = 'World';
|
|
|
|
|
|
|
|
|
|
this.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>
|
2011-06-06 15:50:35 +00:00
|
|
|
</div>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
2011-10-07 18:27:49 +00:00
|
|
|
it('should calculate expression in binding', function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
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
|
2011-11-12 01:15:22 +00:00
|
|
|
built-in method on `Array` called {@link api/angular.module.ng.$filter.filter $filter} and angular doesn't add
|
2011-06-06 15:50:35 +00:00
|
|
|
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
|
2011-11-12 01:15:22 +00:00
|
|
|
they have extra methods. The actual method for `$filter()` is `angular.module.ng.$filter.filter()`. You can
|
2011-06-06 15:50:35 +00:00
|
|
|
call it from JavaScript.
|
|
|
|
|
|
|
|
|
|
Extensions: You can further extend the expression vocabulary by adding new methods to
|
2011-11-12 01:15:22 +00:00
|
|
|
`angular.module.ng.$filter` or `angular.String`, etc.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
<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>
|
2011-09-08 20:56:29 +00:00
|
|
|
Search: <input ng:model="searchText"/>
|
2011-06-06 15:50:35 +00:00
|
|
|
<table class="example3">
|
|
|
|
|
<tr><th>Name</th><th>Phone</th><tr>
|
2011-11-09 01:40:52 +00:00
|
|
|
<tr ng:repeat="friend in friends | filter:searchText">
|
2011-06-06 15:50:35 +00:00
|
|
|
<td>{{friend.name}}</td>
|
|
|
|
|
<td>{{friend.phone}}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
2011-10-07 18:27:49 +00:00
|
|
|
it('should filter the list', function() {
|
2011-06-06 15:50:35 +00:00
|
|
|
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
|
|
|
|
|
|
2011-11-12 01:15:22 +00:00
|
|
|
The expression evaluator simply passes the value of name to angular.module.ng.$filter.uppercase.
|
2011-06-06 15:50:35 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2011-11-12 01:15:22 +00:00
|
|
|
* {@link api/angular.module.ng.$compile Angular Compiler API}
|