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

Commit

Permalink
docs: reorganize information about interpolation
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Narretz committed Dec 31, 2015
1 parent 616695e commit d558dc5
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 105 deletions.
57 changes: 0 additions & 57 deletions docs/content/guide/directive.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -141,63 +141,6 @@ directives when possible.
</div>



### Text and attribute bindings

During the compilation process the {@link ng.$compile compiler} matches text and attributes
using the {@link ng.$interpolate $interpolate} service to see if they contain embedded
expressions. These expressions are registered as {@link ng.$rootScope.Scope#$watch watches}
and will update as part of normal {@link ng.$rootScope.Scope#$digest digest} cycle. An
example of interpolation is shown below:

```html
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
```


### `ngAttr` attribute bindings

Web browsers are sometimes picky about what values they consider valid for attributes.

For example, considering this template:

```html
<svg>
<circle cx="{{cx}}"></circle>
</svg>
```

We would expect Angular to be able to bind to this, but when we check the console we see
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's
restrictions, you cannot simply write `cx="{{cx}}"`.

With `ng-attr-cx` you can work around this problem.

If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
you to bind to attributes that would otherwise be eagerly processed by browsers
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
results in `undefined`, the attribute is removed and not added to the element.

For example, we could fix the example above by instead writing:

```html
<svg>
<circle ng-attr-cx="{{cx}}"></circle>
</svg>
```

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.

For example, to bind to `viewBox`, we can write:

```html
<svg ng-attr-view_box="{{viewBox}}">
</svg>
```


## Creating Directives

First let's talk about the {@link ng.$compileProvider#directive API for registering directives}. Much like
Expand Down
7 changes: 4 additions & 3 deletions docs/content/guide/expression.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

# Angular Expressions

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

For example, these are valid expressions in Angular:

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


### Why this feature
### Reasons for using one-time binding

The main purpose of one-time binding expression is to provide a way to create a binding
that gets deregistered and frees up resources once the binding is stabilized.
Expand Down
142 changes: 142 additions & 0 deletions docs/content/guide/interpolation.ngdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
@ngdoc overview
@name Interpolation
@sortOrder 275
@description

# Interpolation and data-binding

Interpolation markup with embedded @link {guide/expressions expressions} is used by Angular to
provide data-binding to text nodes and attribute values.

An example of interpolation is shown below:

```html
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
```

### How text and attribute bindings work

During the compilation process the {@link ng.$compile compiler} uses the {@link ng.$interpolate $interpolate}
service to see if text nodes and element attributes contain interpolation markup with embedded expressions.

If that is the case, the compiler adds an interpolateDirective to the node and
registers {@link ng.$rootScope.Scope#$watch watches} on the computed interpolation function,
which will update the corresponding text nodes or attribute values as part of the
normal {@link ng.$rootScope.Scope#$digest digest} cycle.

Note that the interpolateDirective has a priority of 100 and sets up the watch in the preLink function.

### Binding to boolean attributes

Attributes such as `disabled` are called `boolean` attributes, because their presence means `true` and
their absence means `false`. We cannot use normal attribute bindings with them, because the HTML
specification does not require browsers to preserve the values of boolean attributes. This means that
if we put an Angular interpolation expression into such an attribute then the binding information
would be lost, because the browser ignores the attribute value.

In the following example, the interpolation information would be ignored and the browser would simply
interpret the attribute as present, meaning that the button would always be disabled.

```html
Disabled: <input type="checkbox" ng-model="isDisabled" />
<button disabled="{{isDisabled}}">Disabled</button>
```

For this reason, Angular provides special `ng`-prefixed directives for the following boolean attributes:
{@link ngDisabled `disabled`}, [@link ngRequired `required`}, [@link ngSelected `selected`},
{@link ngChecked `checked`}, {@link ngReadonly `readOnly`} , and [@link ngOpen `open`}.

These directives take an expression inside the attribute, and set the corresponding boolean attribute
to true when the expression evaluates to truthy.

```html
Disabled: <input type="checkbox" ng-model="isDisabled" />
<button ng-disabled="isDisabled">Disabled</button>
```

### `ngAttr` for binding to arbitrary attributes

Web browsers are sometimes picky about what values they consider valid for attributes.

For example, considering this template:

```html
<svg>
<circle cx="{{cx}}"></circle>
</svg>
```

We would expect Angular to be able to bind to this, but when we check the console we see
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's
restrictions, you cannot simply write `cx="{{cx}}"`.

With `ng-attr-cx` you can work around this problem.

If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
you to bind to attributes that would otherwise be eagerly processed by browsers
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
results in `undefined`, the attribute is removed and not added to the element.

For example, we could fix the example above by instead writing:

```html
<svg>
<circle ng-attr-cx="{{cx}}"></circle>
</svg>
```

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.

For example, to bind to `viewBox`, we can write:

```html
<svg ng-attr-view_box="{{viewBox}}">
</svg>
```

The following attributes are also known to cause problems when used with normal bindings:

- **size** in `<select>` elements (see [Github issue 1619](https://github.com/angular/angular.js/issues/1619))
- **placeholder** in `<textarea>` in Internet Explorer 10/11 (see [Github issue 5025](https://github.com/angular/angular.js/issues/5025))


### Embedding interpolation markup inside expressions

Angular directives take either expressions or interpolation markup with embedded expressions. So the
following example which embeds interpolation inside an expression is a bad practice:

```html
<div ng-show="form{{$index}}.$invalid"></div>
```

You should instead delegate the computation of complex expressions to the scope, like this:

```html
<div ng-show="getForm($index).$invalid"></div>
```

```js
function getForm() {
return $scope['form' + $index];
}
```

You can also access the `scope` with `this` in your templates:

```html
<div ng-show="this['form' + $index].$invalid"></div>
```

#### Why mixing interpolation and expressions is bad practice:

- It increases the complexity of the markup
- There is no guarantee that it works for every directive, because interpolation itself is a directive.
If another directive accesses attribute data before interpolation has run, it will get the raw
interpolation markup and not data.
- It impacts performance, as interpolation adds another watcher to the scope.
- Since this is not recommended usage, we do not test for this, and changes to
Angular core may break your code.
3 changes: 2 additions & 1 deletion src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* @param {string} key Normalized key. (ie ngAttribute) .
* @param {function(interpolatedValue)} fn Function that will be called whenever
the interpolated value of the attribute changes.
* See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
* See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
* guide} for more info.
* @returns {function()} Returns a deregistration function for this observer.
*/
$observe: function(key, fn) {
Expand Down
63 changes: 20 additions & 43 deletions src/ng/directive/attrs.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,7 @@
* {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
*
* A special directive is necessary because we cannot use interpolation inside the `disabled`
* attribute. The following example would make the button enabled on Chrome/Firefox
* but not on older IEs:
*
* ```html
* <!-- See below for an example of ng-disabled being used correctly -->
* <div ng-init="isDisabled = false">
* <button disabled="{{isDisabled}}">Disabled</button>
* </div>
* ```
*
* This is because the HTML specification does not require browsers to preserve the values of
* boolean attributes such as `disabled` (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
*
* @example
<example>
Expand Down Expand Up @@ -211,15 +198,9 @@
* Note that this directive should not be used together with {@link ngModel `ngModel`},
* as this can lead to unexpected behavior.
*
* ### Why do we need `ngChecked`?
* A special directive is necessary because we cannot use interpolation inside the `checked`
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
*
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as checked. (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngChecked` directive solves this problem for the `checked` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
<example>
<file name="index.html">
Expand Down Expand Up @@ -248,13 +229,12 @@
* @priority 100
*
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as readonly. (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngReadonly` directive solves this problem for the `readonly` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy.
*
* A special directive is necessary because we cannot use interpolation inside the `readOnly`
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
*
* @example
<example>
<file name="index.html">
Expand Down Expand Up @@ -283,13 +263,11 @@
* @priority 100
*
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as selected. (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngSelected` directive solves this problem for the `selected` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy.
*
* A special directive is necessary because we cannot use interpolation inside the `selected`
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
*
* @example
<example>
Expand Down Expand Up @@ -321,13 +299,12 @@
* @priority 100
*
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as open. (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngOpen` directive solves this problem for the `open` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy.
*
* A special directive is necessary because we cannot use interpolation inside the `open`
* attribute. See the {@link guide/interpolation interpolation guide} for more info.
*
* @example
<example>
<file name="index.html">
Expand Down
58 changes: 57 additions & 1 deletion src/ng/directive/validators.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,61 @@
'use strict';

/**
* @ngdoc directive
* @name ngRequired
*
* @description
*
* ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}.
* It is most often used for [@link input `input`} and {@link select `select`} controls, but can also be
* applied to custom controls.
*
* The directive sets the `required` attribute on the element if the Angular expression inside
* `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we
* cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide}
* for more info.
*
* The validator will set the `required` error key to true if the `required` attribute is set and
* calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty` with the
* {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the
* `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing
* custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based.
*
* @example
* <example name="ngRequiredDirective" module="ngRequiredExample">
* <file name="index.html">
* <script>
* angular.module('ngRequiredExample', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.required = true;
* }]);
* </script>
* <div ng-controller="ExampleController">
* <form name="form">
* <label for="required">Toggle required: </label>
* <input type="checkbox" ng-model="required" id="required" />
* <br>
* <label for="input">This input must be filled if `required` is true: </label>
* <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br>
* <hr>
* required error set? = <code>{{form.input.$error.required}}</code><br>
* model = <code>{{model}}</code>
* </form>
* </div>
* </file>
* <file name="protractor.js" type="protractor">
* var required = element(by.binding('form.input.$error.required'));
* var model = element(by.binding('model'));
*
* it('should set the required error', function() {
* expect(required.getText()).toContain('true');
*
* element(by.id('input')).sendKeys('123');
* expect(required.getText()).not.toContain('true');
* expect(model.getText()).toContain('123');
* });
* </file>
* </example>
*/
var requiredDirective = function() {
return {
restrict: 'A',
Expand Down

0 comments on commit d558dc5

Please sign in to comment.