Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit d558dc5

Browse files
committed
docs: reorganize information about interpolation
- Move interpolation info from Directive guide into new interpolation guide - Add information about boolean attributes to interpolation guide - remove wroong examples from prefixed boolean attribute docs, link to interpolation guide instead - mention additional examples for attributes that benefit from ngAttr - add docs for ngRequired directive
1 parent 616695e commit d558dc5

File tree

6 files changed

+225
-105
lines changed

6 files changed

+225
-105
lines changed

docs/content/guide/directive.ngdoc

-57
Original file line numberDiff line numberDiff line change
@@ -141,63 +141,6 @@ directives when possible.
141141
</div>
142142

143143

144-
145-
### Text and attribute bindings
146-
147-
During the compilation process the {@link ng.$compile compiler} matches text and attributes
148-
using the {@link ng.$interpolate $interpolate} service to see if they contain embedded
149-
expressions. These expressions are registered as {@link ng.$rootScope.Scope#$watch watches}
150-
and will update as part of normal {@link ng.$rootScope.Scope#$digest digest} cycle. An
151-
example of interpolation is shown below:
152-
153-
```html
154-
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
155-
```
156-
157-
158-
### `ngAttr` attribute bindings
159-
160-
Web browsers are sometimes picky about what values they consider valid for attributes.
161-
162-
For example, considering this template:
163-
164-
```html
165-
<svg>
166-
<circle cx="{{cx}}"></circle>
167-
</svg>
168-
```
169-
170-
We would expect Angular to be able to bind to this, but when we check the console we see
171-
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's
172-
restrictions, you cannot simply write `cx="{{cx}}"`.
173-
174-
With `ng-attr-cx` you can work around this problem.
175-
176-
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
177-
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
178-
you to bind to attributes that would otherwise be eagerly processed by browsers
179-
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
180-
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
181-
results in `undefined`, the attribute is removed and not added to the element.
182-
183-
For example, we could fix the example above by instead writing:
184-
185-
```html
186-
<svg>
187-
<circle ng-attr-cx="{{cx}}"></circle>
188-
</svg>
189-
```
190-
191-
If one wants to modify a camelcased attribute (SVG elements have valid camelcased attributes), such as `viewBox` on the `svg` element, one can use underscores to denote that the attribute to bind to is naturally camelcased.
192-
193-
For example, to bind to `viewBox`, we can write:
194-
195-
```html
196-
<svg ng-attr-view_box="{{viewBox}}">
197-
</svg>
198-
```
199-
200-
201144
## Creating Directives
202145

203146
First let's talk about the {@link ng.$compileProvider#directive API for registering directives}. Much like

docs/content/guide/expression.ngdoc

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
# Angular Expressions
77

8-
Angular expressions are JavaScript-like code snippets that are usually placed in bindings such as
9-
`{{ expression }}`.
8+
Angular expressions are JavaScript-like code snippets that are mainly placed in
9+
interpolation bindings such as `<span title="{{ attrBinding }}">{{ textBinding }}</span>`,
10+
but also used directly in directive attributes such as `ng-click="functionExpression()"`.
1011

1112
For example, these are valid expressions in Angular:
1213

@@ -282,7 +283,7 @@ result is a non-undefined value (see value stabilization algorithm below).
282283
</example>
283284

284285

285-
### Why this feature
286+
### Reasons for using one-time binding
286287

287288
The main purpose of one-time binding expression is to provide a way to create a binding
288289
that gets deregistered and frees up resources once the binding is stabilized.
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
@ngdoc overview
2+
@name Interpolation
3+
@sortOrder 275
4+
@description
5+
6+
# Interpolation and data-binding
7+
8+
Interpolation markup with embedded @link {guide/expressions expressions} is used by Angular to
9+
provide data-binding to text nodes and attribute values.
10+
11+
An example of interpolation is shown below:
12+
13+
```html
14+
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
15+
```
16+
17+
### How text and attribute bindings work
18+
19+
During the compilation process the {@link ng.$compile compiler} uses the {@link ng.$interpolate $interpolate}
20+
service to see if text nodes and element attributes contain interpolation markup with embedded expressions.
21+
22+
If that is the case, the compiler adds an interpolateDirective to the node and
23+
registers {@link ng.$rootScope.Scope#$watch watches} on the computed interpolation function,
24+
which will update the corresponding text nodes or attribute values as part of the
25+
normal {@link ng.$rootScope.Scope#$digest digest} cycle.
26+
27+
Note that the interpolateDirective has a priority of 100 and sets up the watch in the preLink function.
28+
29+
### Binding to boolean attributes
30+
31+
Attributes such as `disabled` are called `boolean` attributes, because their presence means `true` and
32+
their absence means `false`. We cannot use normal attribute bindings with them, because the HTML
33+
specification does not require browsers to preserve the values of boolean attributes. This means that
34+
if we put an Angular interpolation expression into such an attribute then the binding information
35+
would be lost, because the browser ignores the attribute value.
36+
37+
In the following example, the interpolation information would be ignored and the browser would simply
38+
interpret the attribute as present, meaning that the button would always be disabled.
39+
40+
```html
41+
Disabled: <input type="checkbox" ng-model="isDisabled" />
42+
<button disabled="{{isDisabled}}">Disabled</button>
43+
```
44+
45+
For this reason, Angular provides special `ng`-prefixed directives for the following boolean attributes:
46+
{@link ngDisabled `disabled`}, [@link ngRequired `required`}, [@link ngSelected `selected`},
47+
{@link ngChecked `checked`}, {@link ngReadonly `readOnly`} , and [@link ngOpen `open`}.
48+
49+
These directives take an expression inside the attribute, and set the corresponding boolean attribute
50+
to true when the expression evaluates to truthy.
51+
52+
```html
53+
Disabled: <input type="checkbox" ng-model="isDisabled" />
54+
<button ng-disabled="isDisabled">Disabled</button>
55+
```
56+
57+
### `ngAttr` for binding to arbitrary attributes
58+
59+
Web browsers are sometimes picky about what values they consider valid for attributes.
60+
61+
For example, considering this template:
62+
63+
```html
64+
<svg>
65+
<circle cx="{{cx}}"></circle>
66+
</svg>
67+
```
68+
69+
We would expect Angular to be able to bind to this, but when we check the console we see
70+
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's
71+
restrictions, you cannot simply write `cx="{{cx}}"`.
72+
73+
With `ng-attr-cx` you can work around this problem.
74+
75+
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
76+
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
77+
you to bind to attributes that would otherwise be eagerly processed by browsers
78+
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
79+
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
80+
results in `undefined`, the attribute is removed and not added to the element.
81+
82+
For example, we could fix the example above by instead writing:
83+
84+
```html
85+
<svg>
86+
<circle ng-attr-cx="{{cx}}"></circle>
87+
</svg>
88+
```
89+
90+
If one wants to modify a camelcased attribute (SVG elements have valid camelcased attributes),
91+
such as `viewBox` on the `svg` element, one can use underscores to denote that the attribute to bind
92+
to is naturally camelcased.
93+
94+
For example, to bind to `viewBox`, we can write:
95+
96+
```html
97+
<svg ng-attr-view_box="{{viewBox}}">
98+
</svg>
99+
```
100+
101+
The following attributes are also known to cause problems when used with normal bindings:
102+
103+
- **size** in `<select>` elements (see [Github issue 1619](https://github.com/angular/angular.js/issues/1619))
104+
- **placeholder** in `<textarea>` in Internet Explorer 10/11 (see [Github issue 5025](https://github.com/angular/angular.js/issues/5025))
105+
106+
107+
### Embedding interpolation markup inside expressions
108+
109+
Angular directives take either expressions or interpolation markup with embedded expressions. So the
110+
following example which embeds interpolation inside an expression is a bad practice:
111+
112+
```html
113+
<div ng-show="form{{$index}}.$invalid"></div>
114+
```
115+
116+
You should instead delegate the computation of complex expressions to the scope, like this:
117+
118+
```html
119+
<div ng-show="getForm($index).$invalid"></div>
120+
```
121+
122+
```js
123+
function getForm() {
124+
return $scope['form' + $index];
125+
}
126+
```
127+
128+
You can also access the `scope` with `this` in your templates:
129+
130+
```html
131+
<div ng-show="this['form' + $index].$invalid"></div>
132+
```
133+
134+
#### Why mixing interpolation and expressions is bad practice:
135+
136+
- It increases the complexity of the markup
137+
- There is no guarantee that it works for every directive, because interpolation itself is a directive.
138+
If another directive accesses attribute data before interpolation has run, it will get the raw
139+
interpolation markup and not data.
140+
- It impacts performance, as interpolation adds another watcher to the scope.
141+
- Since this is not recommended usage, we do not test for this, and changes to
142+
Angular core may break your code.

src/ng/compile.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1199,7 +1199,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
11991199
* @param {string} key Normalized key. (ie ngAttribute) .
12001200
* @param {function(interpolatedValue)} fn Function that will be called whenever
12011201
the interpolated value of the attribute changes.
1202-
* See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
1202+
* See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
1203+
* guide} for more info.
12031204
* @returns {function()} Returns a deregistration function for this observer.
12041205
*/
12051206
$observe: function(key, fn) {

src/ng/directive/attrs.js

+20-43
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,7 @@
163163
* {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
164164
*
165165
* A special directive is necessary because we cannot use interpolation inside the `disabled`
166-
* attribute. The following example would make the button enabled on Chrome/Firefox
167-
* but not on older IEs:
168-
*
169-
* ```html
170-
* <!-- See below for an example of ng-disabled being used correctly -->
171-
* <div ng-init="isDisabled = false">
172-
* <button disabled="{{isDisabled}}">Disabled</button>
173-
* </div>
174-
* ```
175-
*
176-
* This is because the HTML specification does not require browsers to preserve the values of
177-
* boolean attributes such as `disabled` (Their presence means true and their absence means false.)
178-
* If we put an Angular interpolation expression into such an attribute then the
179-
* binding information would be lost when the browser removes the attribute.
166+
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
180167
*
181168
* @example
182169
<example>
@@ -211,15 +198,9 @@
211198
* Note that this directive should not be used together with {@link ngModel `ngModel`},
212199
* as this can lead to unexpected behavior.
213200
*
214-
* ### Why do we need `ngChecked`?
201+
* A special directive is necessary because we cannot use interpolation inside the `checked`
202+
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
215203
*
216-
* The HTML specification does not require browsers to preserve the values of boolean attributes
217-
* such as checked. (Their presence means true and their absence means false.)
218-
* If we put an Angular interpolation expression into such an attribute then the
219-
* binding information would be lost when the browser removes the attribute.
220-
* The `ngChecked` directive solves this problem for the `checked` attribute.
221-
* This complementary directive is not removed by the browser and so provides
222-
* a permanent reliable place to store the binding information.
223204
* @example
224205
<example>
225206
<file name="index.html">
@@ -248,13 +229,12 @@
248229
* @priority 100
249230
*
250231
* @description
251-
* The HTML specification does not require browsers to preserve the values of boolean attributes
252-
* such as readonly. (Their presence means true and their absence means false.)
253-
* If we put an Angular interpolation expression into such an attribute then the
254-
* binding information would be lost when the browser removes the attribute.
255-
* The `ngReadonly` directive solves this problem for the `readonly` attribute.
256-
* This complementary directive is not removed by the browser and so provides
257-
* a permanent reliable place to store the binding information.
232+
*
233+
* Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy.
234+
*
235+
* A special directive is necessary because we cannot use interpolation inside the `readOnly`
236+
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
237+
*
258238
* @example
259239
<example>
260240
<file name="index.html">
@@ -283,13 +263,11 @@
283263
* @priority 100
284264
*
285265
* @description
286-
* The HTML specification does not require browsers to preserve the values of boolean attributes
287-
* such as selected. (Their presence means true and their absence means false.)
288-
* If we put an Angular interpolation expression into such an attribute then the
289-
* binding information would be lost when the browser removes the attribute.
290-
* The `ngSelected` directive solves this problem for the `selected` attribute.
291-
* This complementary directive is not removed by the browser and so provides
292-
* a permanent reliable place to store the binding information.
266+
*
267+
* Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy.
268+
*
269+
* A special directive is necessary because we cannot use interpolation inside the `selected`
270+
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
293271
*
294272
* @example
295273
<example>
@@ -321,13 +299,12 @@
321299
* @priority 100
322300
*
323301
* @description
324-
* The HTML specification does not require browsers to preserve the values of boolean attributes
325-
* such as open. (Their presence means true and their absence means false.)
326-
* If we put an Angular interpolation expression into such an attribute then the
327-
* binding information would be lost when the browser removes the attribute.
328-
* The `ngOpen` directive solves this problem for the `open` attribute.
329-
* This complementary directive is not removed by the browser and so provides
330-
* a permanent reliable place to store the binding information.
302+
*
303+
* Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy.
304+
*
305+
* A special directive is necessary because we cannot use interpolation inside the `open`
306+
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
307+
*
331308
* @example
332309
<example>
333310
<file name="index.html">

src/ng/directive/validators.js

+57-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,61 @@
11
'use strict';
2-
2+
/**
3+
* @ngdoc directive
4+
* @name ngRequired
5+
*
6+
* @description
7+
*
8+
* ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
9+
* It is most often used for [@link input `input`} and {@link select `select`} controls, but can also be
10+
* applied to custom controls.
11+
*
12+
* The directive sets the `required` attribute on the element if the Angular expression inside
13+
* `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we
14+
* cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide}
15+
* for more info.
16+
*
17+
* The validator will set the `required` error key to true if the `required` attribute is set and
18+
* calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty` with the
19+
* {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the
20+
* `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing
21+
* custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based.
22+
*
23+
* @example
24+
* <example name="ngRequiredDirective" module="ngRequiredExample">
25+
* <file name="index.html">
26+
* <script>
27+
* angular.module('ngRequiredExample', [])
28+
* .controller('ExampleController', ['$scope', function($scope) {
29+
* $scope.required = true;
30+
* }]);
31+
* </script>
32+
* <div ng-controller="ExampleController">
33+
* <form name="form">
34+
* <label for="required">Toggle required: </label>
35+
* <input type="checkbox" ng-model="required" id="required" />
36+
* <br>
37+
* <label for="input">This input must be filled if `required` is true: </label>
38+
* <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br>
39+
* <hr>
40+
* required error set? = <code>{{form.input.$error.required}}</code><br>
41+
* model = <code>{{model}}</code>
42+
* </form>
43+
* </div>
44+
* </file>
45+
* <file name="protractor.js" type="protractor">
46+
* var required = element(by.binding('form.input.$error.required'));
47+
* var model = element(by.binding('model'));
48+
*
49+
* it('should set the required error', function() {
50+
* expect(required.getText()).toContain('true');
51+
*
52+
* element(by.id('input')).sendKeys('123');
53+
* expect(required.getText()).not.toContain('true');
54+
* expect(model.getText()).toContain('123');
55+
* });
56+
* </file>
57+
* </example>
58+
*/
359
var requiredDirective = function() {
460
return {
561
restrict: 'A',

0 commit comments

Comments
 (0)