forked from angular/code.angularjs.org
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdev_guide.expressions.html
212 lines (176 loc) · 9.34 KB
/
dev_guide.expressions.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
<h1>Developer Guide: Understanding Angular Expressions</h1>
<div class="developer-guide-understanding-angular-expressions"><fieldset class="workInProgress"><legend>Work in Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.</fieldset>
<p>Expressions are <a href="#!/guide/dev_guide.templates.databinding">bindings</a> 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.</p>
<p>For example, these are all valid expressions in angular:</p>
<ul>
<li><code>1+2={{1+2}}</code></li>
<li><code>3*10|currency</code></li>
<li><code>Hello {{name}}!</code></li>
<li><code>Hello {{'World'}}!</code></li>
</ul>
<h3>Angular Expressions vs. JS Expressions</h3>
<p>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:</p>
<ul>
<li><strong>Attribute Evaluation:</strong> evaluation of all attributes are against the current scope, not to the
global window as in JavaScript.</li>
<li><strong>Forgiving:</strong> expression evaluation is forgiving to undefined and null, unlike in JavaScript.</li>
<li><strong>No Control Flow Statements:</strong> you cannot do the following from an angular expression:
conditionals, loops, or throw.</li>
<li><strong>Type Augmentation:</strong> the scope expression evaluator augments built-in types.</li>
<li><strong>Filters:</strong> you can add filters to an expression, for example to convert raw data into a
human-readable format.</li>
<li><strong>The $:</strong> angular reserves this prefix to differentiate its API names from others.</li>
</ul>
<p>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 <code>eval()</code> an angular expression from JavaScript, use
the <code>Scope:$eval()</code> method.</p>
<h3>Example</h3><doc:example>
<pre class="doc-source">
1+2={{1+2}}
</pre>
<pre class="doc-scenario">
it('should calculate expression in binding', function(){
expect(binding('1+2')).toEqual('3');
});
</pre>
</doc:example><p>You can try evaluating different expressions here:</p><doc:example>
<pre class="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>
</pre>
<pre class="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"]);
});
</pre>
</doc:example><h2>Attribute Evaluation</h2>
<p>Evaluation of all attributes takes place against the current scope. Unlike JavaScript, where names
default to global window properties, angular expressions have to use <code>$window</code> to refer to the
global object. For example, if you want to call <code>alert()</code>, which is defined on <code>window</code>, an
expression must use <code>$window.alert()</code>. This is done intentionally to prevent accidental access to
the global state (a common source of subtle bugs).</p><doc:example>
<pre class="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>
</pre>
<pre class="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');
});
</pre>
</doc:example><h3>Forgiving</h3>
<p>Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating <code>a.b.c</code> throws
an exception if <code>a</code> 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:</p>
<pre><code> {{a.b.c}}
</code></pre>
<p>It makes more sense to show nothing than to throw an exception if <code>a</code> 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: <code>{{((a||{}).b||{}).c}}</code></p>
<p>Similarly, invoking a function <code>a.b.c()</code> on undefined or null simply returns undefined.</p>
<p>Assignments work the same way in reverse:</p>
<pre><code> a.b.c = 10
</code></pre>
<p>...creates the intermediary objects even if a is undefined.</p>
<h3>No Control Flow Statements</h3>
<p>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.</p>
<h3>Type Augmentation</h3>
<p>Built-in types have methods like <code>[].push()</code>, 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 <code>Array:$filter()</code>. There is no
built-in method on <code>Array</code> called <a href="#!/api/angular.Array.filter"><code>$filter</code></a> and angular doesn't add
it to <code>Array.prototype</code> because that could collide with other JavaScript frameworks.</p>
<p>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 <code>$filter()</code> is <code>angular.Array.filter()</code>. You can
call it from JavaScript.</p>
<p>Extensions: You can further extend the expression vocabulary by adding new methods to
<code>angular.Array</code> or <code>angular.String</code>, etc.</p><doc:example>
<pre class="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>
</pre>
<pre class="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);
});
</pre>
</doc:example><h3>Filters</h3>
<p>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:</p>
<pre><code> name | uppercase
</code></pre>
<p>The expression evaluator simply passes the value of name to angular.filter.uppercase.</p>
<p>Chain filters using this syntax:</p>
<pre><code> value | filter1 | filter2
</code></pre>
<p>You can also pass colon-delimited arguments to filters, for example, to display the number 123 with
2 decimal points:</p>
<pre><code> 123 | number:2
</code></pre>
<h2>The $</h2>
<p>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
<code>a.length()</code> would return undefined because neither a nor angular define such a property.</p>
<p>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.</p>
<h3>Related Topics</h3>
<ul>
<li><a href="#!/guide/dev_guide.compiler.markup">Understanding Angular Markup</a></li>
<li><a href="#!/guide/dev_guide.templates.filters">Understanding Angular Filters</a></li>
</ul>
<h3>Related API</h3>
<ul>
<li><a href="#!/api/angular.compile"><code>Angular Compiler API</code></a></li>
</ul></div>