590 lines
18 KiB
Markdown
590 lines
18 KiB
Markdown
|
# Templates
|
||
|
|
||
|
Templates are markup which is added to HTML to declarativly describe how the application model should be
|
||
|
projected to DOM as well as which DOM events should invoke which methods on the controller. Templates contain
|
||
|
syntax which is core to Angular and allows for data-binding, event-binding, template-instantiation.
|
||
|
|
||
|
The design of template syntax has these properties:
|
||
|
|
||
|
* All data-binding expressions are easily identifiable. (i.e. there is never an ambiguity wether the value should be
|
||
|
interpreted as string literal or as an expression.)
|
||
|
* All events and their statments are easily identifiable.
|
||
|
* All places of DOM instantiation are easily identifiable.
|
||
|
* All places of variable declaration is esily identifiable.
|
||
|
|
||
|
The above properties guarantee that the templates are easy to parse by tools (such as IDEs) and reason about by people.
|
||
|
At no point is it necessary to understand which directives are active and what are their symantics in order to reason
|
||
|
about the template runtime characteristics.
|
||
|
|
||
|
|
||
|
|
||
|
## Summary
|
||
|
|
||
|
Below is a summary of the kinds of syntaxes which Angular templating supports. The syntaxes are explained in more
|
||
|
detail in fallowing sections.
|
||
|
|
||
|
<table>
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th>Description</th><th>Short</th><th>Canonical</th>
|
||
|
</tr>
|
||
|
<thead>
|
||
|
<tbody>
|
||
|
<tr>
|
||
|
<th>Text Interpolation</th>
|
||
|
<td>
|
||
|
`<div>{{exp}}</div>`
|
||
|
|
||
|
Example:
|
||
|
```
|
||
|
<div>
|
||
|
Hello {{name}}!
|
||
|
<br>
|
||
|
Goodbye {{name}}!
|
||
|
</div>
|
||
|
```
|
||
|
</td>
|
||
|
<td>
|
||
|
`<div [text|index]=exp>`
|
||
|
|
||
|
Example:
|
||
|
```
|
||
|
<div
|
||
|
[text|0]=" 'Hello' + stringify(name) + '!' "
|
||
|
[text|2]=" 'Goodbye' + stringify(name) + '!' ">
|
||
|
_<b>x</b>_
|
||
|
</div>
|
||
|
```
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Property Interpolation</th>
|
||
|
<td>
|
||
|
`<div name="{{exp}}">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
`<div class="{{selected}}">`
|
||
|
</td>
|
||
|
<td>
|
||
|
`<div [name]="stringify(exp)">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
`<div [class]="stringify(selected)">`
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Property binding</th>
|
||
|
<td>
|
||
|
`<div [prop]="exp">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
`<div [hidden]="true">`
|
||
|
</td>
|
||
|
<td>
|
||
|
`<div bind-prop="exp">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
`<div bind-hidden="true">`
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Event binding (non-bubbling)</th>
|
||
|
<td>
|
||
|
`<div (event)="statement">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
`<div (click)="doX()">`
|
||
|
</td>
|
||
|
<td>
|
||
|
`<div on-event="statement">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
`<div on-click="doX()">`
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Event binding (bubbling)</th>
|
||
|
<td>
|
||
|
`<div (^event)="statement">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
`<div (^mouseover)="hlite()">`
|
||
|
</td>
|
||
|
<td>
|
||
|
`<div on-bubble-event="statement">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
`<div on-bubble-mouseover="hlite()">`
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Declare reference</th>
|
||
|
<td>
|
||
|
`<div #symbol>`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
```
|
||
|
<video #player>
|
||
|
<button (click)="player.play()">play</button>
|
||
|
```
|
||
|
</td>
|
||
|
<td>
|
||
|
`<div def="symbol">`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
```
|
||
|
<video def="player">
|
||
|
<button on-click="player.play()">play</button>
|
||
|
```
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Inline Template</th>
|
||
|
<td>
|
||
|
`<div template="...">...</div>`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
```
|
||
|
<ul>
|
||
|
<li template="ng-repeat: #item in items">
|
||
|
{{item}}
|
||
|
</li>
|
||
|
</ul>
|
||
|
```
|
||
|
</td>
|
||
|
<td>
|
||
|
`<template>...</template>`
|
||
|
|
||
|
Example:
|
||
|
```
|
||
|
<ul>
|
||
|
<template def-ng-repeat:"item"
|
||
|
bind-ng-repeat-in="items">
|
||
|
<li>
|
||
|
{{item}}
|
||
|
</li>
|
||
|
</template>
|
||
|
</ul>
|
||
|
```
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Explicit Template</th>
|
||
|
<td>
|
||
|
`<template>...</template>`
|
||
|
|
||
|
Example:
|
||
|
|
||
|
```
|
||
|
<template #ng-repeat="item"
|
||
|
[ng-repeat-in]="items">
|
||
|
_some_content_to_repeat_
|
||
|
</template>
|
||
|
```
|
||
|
</td>
|
||
|
<td>
|
||
|
`<template>...</template>`
|
||
|
|
||
|
Example:
|
||
|
```
|
||
|
<template def-ng-repeat="item"
|
||
|
bind-ng-repeat-in="items">
|
||
|
_some_content_to_repeat_
|
||
|
</template>
|
||
|
```
|
||
|
</td>
|
||
|
</tr>
|
||
|
</tbody>
|
||
|
</table>
|
||
|
|
||
|
|
||
|
|
||
|
## Property Binding
|
||
|
|
||
|
Binding application model data to the UI, is the most common kinds of bindings in an Angular application. The bindings
|
||
|
are always in the form of `property-name` which is assigned an `expression`. The generic form is:
|
||
|
|
||
|
<table>
|
||
|
<tr>
|
||
|
<th>Short form</th>
|
||
|
<td>`<some-element [some-property]="expression">`</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Canonical form</th>
|
||
|
<td>`<some-element bind-some-property="expression">`</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
|
||
|
|
||
|
Where:
|
||
|
* `some-element` can be any existing DOM element.
|
||
|
* `some-property` (escaped with `[]` or `bind-`) is the name of the property on `some-element`. In this case the
|
||
|
dash-case is converted into camel-case `someProperty`.
|
||
|
* `expression` is a valid expression (as defined in section below).
|
||
|
|
||
|
Example:
|
||
|
```
|
||
|
<div [title]="user.firstName">
|
||
|
```
|
||
|
|
||
|
In the above example the `title` property of the `div` element will be updated whenever the `user.firstName` changes
|
||
|
its value.
|
||
|
|
||
|
Key points:
|
||
|
* The binding is to the element property not the element attribute.
|
||
|
* To prevent custom element from accidentaly reading the literal `expression` on the title element, the attribute name
|
||
|
is escaped. In our case the `title` is escaped to `[title]` through the addition of squre brackets `[]`.
|
||
|
* A binding value (in this case `user.firstName` will always be an expression, never a string literal)
|
||
|
|
||
|
NOTE: Unlike Angular v1, Angular v2 binds to properties of elements rather than attributes of elements. This is
|
||
|
done to better support custom elements, and allow binding for values other than strings.
|
||
|
|
||
|
NOTE: Some editors/server side pre-processors may have trouble generating `[]` arround the attribute name. For this
|
||
|
reason Angular also supports a canonical version which is prefixed using `bind-`.
|
||
|
|
||
|
|
||
|
|
||
|
### String Interpolation
|
||
|
|
||
|
Property bindings are the only data bindings which angular supports, but for convenience Angular supports interpolation
|
||
|
syntax which is just a short hand for the data binding syntax.
|
||
|
|
||
|
```
|
||
|
<span>Hello {{name}}!</span>
|
||
|
```
|
||
|
|
||
|
is a short hand for:
|
||
|
|
||
|
```
|
||
|
<span [text|0]=" 'Hello ' + stringify(name) + '!' ">_</span>
|
||
|
```
|
||
|
|
||
|
The above says to bind `'Hello ' + stringify(name) + '!'` expression to the zero-th child of the `span`'s `text`
|
||
|
property. The index is necessary in case there are more than one text nodes, or if the text node we wish to bind to
|
||
|
is not the first one.
|
||
|
|
||
|
Similarly the same rules apply to interpolation inside attributes.
|
||
|
|
||
|
```
|
||
|
<span title="Hello {{name}}!"></span>
|
||
|
```
|
||
|
|
||
|
is a short hand for:
|
||
|
|
||
|
```
|
||
|
<span [title]=" 'Hello ' + stringify(name) + '!' "></span>
|
||
|
```
|
||
|
|
||
|
NOTE: `stringify()` is a built in implicit function which converts its argument to a string representation, while
|
||
|
keeping `null` and `undefined` as empty strings.
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## Local Varibles
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## Inline Templates
|
||
|
|
||
|
Data binding allows updating the DOM's properties, but it does not allow for changing of the DOM structure. To change
|
||
|
DOM structure we need the ability to define child templates, and than instantiat these templates into Views. The
|
||
|
Views can than be inserted and removed as needed to change the DOM structure.
|
||
|
|
||
|
<table>
|
||
|
<tr>
|
||
|
<th>Short form</th>
|
||
|
<td>
|
||
|
```
|
||
|
parent template
|
||
|
<element>
|
||
|
<some-element template="instantiating-directive-microsyntax">child template</some-element>
|
||
|
</element>
|
||
|
```
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Canonical form</th>
|
||
|
<td>
|
||
|
```
|
||
|
parent template
|
||
|
<element>
|
||
|
<template instantiating-directive-bindings>
|
||
|
<some-element>child template</some-element>
|
||
|
</template>
|
||
|
</element>
|
||
|
```
|
||
|
</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
|
||
|
Where:
|
||
|
* `template` defines a child template and designates the anchor where Views (instances of the template) will be
|
||
|
inserted. The template can be defined implicitly with `template` attribute, which turns the current element into
|
||
|
a template, or explicitly with `<template>` element. Explicit declaration is longer, but it allows for having
|
||
|
templates which have more than one root DOM node.
|
||
|
* `instantiating-directive` is required for templates. The instantiating directive is responsible for deciding when
|
||
|
and in which order should child views be inserted into this location. An instantiating directive usually has one or
|
||
|
more bindings and can be represnted as either `instantiating-directive-bindings` or
|
||
|
`instantiating-directive-microsyntax` on `template` element or attribute. See template microsyntax for more details.
|
||
|
|
||
|
|
||
|
Example of conditionally included template:
|
||
|
|
||
|
```
|
||
|
Hello {{user}}!
|
||
|
<div template="ng-if: isAdimnistrator">
|
||
|
...administrator menu here...
|
||
|
</div>
|
||
|
```
|
||
|
|
||
|
In the above example the `ng-if` instantiator determins if the child view (an instance of the child template) should be
|
||
|
inserted into ther root view. The `ng-if` makes this decision based on if the `isAdimnistrator` binding is true.
|
||
|
|
||
|
The above example is in the shart form, for better clarity let's rewrite it in the canonical form, which is functionaly
|
||
|
identical.
|
||
|
|
||
|
```
|
||
|
Hello {{user}}!
|
||
|
<template [ng-if]="isAdimnistrator">
|
||
|
<div>
|
||
|
...administrator menu here...
|
||
|
</div>
|
||
|
</template>
|
||
|
```
|
||
|
|
||
|
NOTE: Only Instantiator directives can be placed on the template element. (Decorators, and Components are not allowed.)
|
||
|
|
||
|
|
||
|
### Template Microsyntax
|
||
|
|
||
|
Often times it is necessary to encode a lot of different bindings into a template to controll how the instantiation
|
||
|
of the templates occures. One such example is ng-repeat.
|
||
|
|
||
|
```
|
||
|
<form #foo=form>
|
||
|
</form>
|
||
|
<ul>
|
||
|
<template ng-repeat #person [in]="people" #i="index">
|
||
|
<li>{{i}}. {{item}}<li>
|
||
|
</template>
|
||
|
</ul>
|
||
|
```
|
||
|
|
||
|
Where:
|
||
|
* `ng-repeat` triggers the ng-repeat directive.
|
||
|
* `[in]="people"` binds to an iterable object to the `ng-repeat` controller.
|
||
|
* `#person` exports the implicit `ng-repeat` item.
|
||
|
* `#i=index` exports item index as `i`.
|
||
|
|
||
|
The above example is explicit but quite wordy, for this reason in most situatios a short hand version of the
|
||
|
syntax is prefferable.
|
||
|
|
||
|
```
|
||
|
<ul>
|
||
|
<li template="ng-repeat; #person; in=people; #i=index;">{{i}}. {{item}}<li>
|
||
|
</ul>
|
||
|
```
|
||
|
|
||
|
Notice how each key value pair is translated to `key=value;` statement in the `template` attribute. This makes the
|
||
|
repeat syntax a much shorter, but we can do better. Turns out that most punctuation is opional in the short version
|
||
|
which allows us to further shorten the text.
|
||
|
|
||
|
```
|
||
|
<ul>
|
||
|
<li template="ng-repeat #person in people #i=index">{{i}}. {{item}}<li>
|
||
|
</ul>
|
||
|
```
|
||
|
|
||
|
We can also optionaly use `var` instead of `#` and add `:` to `ng-repeat` which creates the fallowing recomended
|
||
|
microsyntax for `ng-repeat`.
|
||
|
|
||
|
```
|
||
|
<ul>
|
||
|
<li template="ng-repeat: var person in people; var i=index">{{i}}. {{item}}<li>
|
||
|
</ul>
|
||
|
```
|
||
|
|
||
|
The format is intentionally defined freely, so that developers of directives can build expressive microsyntax for
|
||
|
their directives. Fallowing describes a more formal definition.
|
||
|
|
||
|
```
|
||
|
expression: ... // as defined in Expressions section
|
||
|
local: [a-zA-Z][a-zA-Z0-9]* // exported variable name available for binding
|
||
|
internal: [a-zA-Z][a-zA-Z0-9]* // internal variable name which the directive exports.
|
||
|
key: [a-z][-|_|a-z0-9]]* // key which maps to attribute name
|
||
|
keyExpression: key[:|=]?expression // binding which maps an expression to a property
|
||
|
varExport: [#|var]local(=internal)? // binding which exports a local variable from a directive
|
||
|
microsyntax: ([[key|keyExpression|varExport][;|,]?)*
|
||
|
```
|
||
|
|
||
|
Where
|
||
|
* `expression` is an angular expression as defined in section: Expressions
|
||
|
* `local` is a local identifiar for local variables.
|
||
|
* `internal` is an internal variable which the directive exports for binding.
|
||
|
* `key` is an attribute name usually only used to trigger a specific directive.
|
||
|
* `keyExpression` is an property name to which the epression will be bound to.
|
||
|
* `varExport` allows exporting of directive internal state as varibles for further binding. If no `internal` name
|
||
|
is specified than the exporting is to an implicit variable.
|
||
|
* `microsyntax` allows you to build simple microsyntax which can still clearly identify which expressions bind to
|
||
|
which properties as well as which varibales are exported for binding.
|
||
|
|
||
|
|
||
|
NOTE: the `template` attribute must be present to make it clear to the user that a sub-template is being created. This
|
||
|
goes allong the philosophy that the developer should be able to reason about the template without understanding the
|
||
|
semantics of the instantiator directive.
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## Binding Events
|
||
|
|
||
|
Binding events allows wiring events from DOM (or other components) to the Angular controller.
|
||
|
|
||
|
<table>
|
||
|
<tr>
|
||
|
<th>Short form</th>
|
||
|
<td>`<some-element (some-event)="statement">`</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Canonical form</th>
|
||
|
<td>`<some-element on-some-event="statement">`</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
|
||
|
Where:
|
||
|
* `some-element` Any element which can generate DOM events (or has angular directive which generates the event).
|
||
|
* `some-event` (escaped with `()` or `bind-`) is the name of the event `some-event`. In this case the
|
||
|
dash-case is converted into camel-case `someEvent`.
|
||
|
* `statement` is a valid statement (as defined in section below).
|
||
|
|
||
|
By default angular anly listens to the element on the event, and ignores events which bubble. To listen to bubbled
|
||
|
events (as in the case of clicking on any child) use the bubble option (`(^event)` or `on-bubble-event`) as shown
|
||
|
bellow.
|
||
|
|
||
|
<table>
|
||
|
<tr>
|
||
|
<th>Short form</th>
|
||
|
<td>`<some-element (^some-event)="statement">`</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Canonical form</th>
|
||
|
<td>`<some-element on-bubble-some-event="statement">`</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
|
||
|
|
||
|
Example:
|
||
|
```
|
||
|
@Component(...)
|
||
|
class Example {
|
||
|
submit() {
|
||
|
// do something when button is clicked
|
||
|
}
|
||
|
}
|
||
|
|
||
|
<button (click)="submit()">Submit</button>
|
||
|
```
|
||
|
|
||
|
In the above example, when clicking on the submit button angular will invoke the `submit` method on the surounding
|
||
|
component's controller.
|
||
|
|
||
|
|
||
|
NOTE: Unlike Angular v1, Angular v2 treats event bindings as core constructs not as directives. This means that there
|
||
|
is no need to create a event directive for each kind of event. This makes it possible for Angular v2 to easily
|
||
|
bind to custom events of Custom Elements, whos event names are not known ahead of time.
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## Expressions, Statements and Formatters
|
||
|
|
||
|
Angular templates contain expressions for binding to data and statments for binding to events. Expressions and statments
|
||
|
have different semantics.
|
||
|
|
||
|
|
||
|
### Expressions
|
||
|
|
||
|
Expressions can be used to bind to a properties only. Expressions represent how data should be projected to the View.
|
||
|
Expressions should not have any sideeffects and should be idempotent. Examples of where expressions can be used in
|
||
|
Angular are:
|
||
|
```
|
||
|
<div title="{{expression}}">{{expression}}</div>
|
||
|
<div [title]="expression">...</div>
|
||
|
<div bind-title="expression">...</div>
|
||
|
<div template="ng-if: expression">...</div>
|
||
|
```
|
||
|
|
||
|
Expressions are simplified version of expression in the langugage in which you are writing your application. (i.e.
|
||
|
expressions fallow JS syntax and semantics in JS and Dart syntax and semantics in Dart). Unlike expressions in the
|
||
|
langugage, binding expressions behave differently in following ways:
|
||
|
|
||
|
* *Must be defined*: Anlike Angular v1, Angular v2 will throw an error on dereferencing fields which are not defined.
|
||
|
For example: `user.name` will throw an error if `user` is defined but it does not have `name` property. If the `name`
|
||
|
property is not known, it must be declared and set to some value such as empty string, `null` (or `undefined` in JS).
|
||
|
This is done to allow early detection of errors in the templates.
|
||
|
* *Safe dereference*: Expressions `user.name` where `user` is null will throw `NullPointerException` in the langugage.
|
||
|
In contrast Angular will silently ignore `null` on `user`. This is done because Views often have to wait for the data
|
||
|
to arrive from the backend and many fields will be null until the data arrives. Safe dereference so cammon in the
|
||
|
Views, that we have made it the default.
|
||
|
* *Single expression*: An expression must be a single statemnet. (i.e. no `;`)
|
||
|
* *No assignments*: Binding expressions can not contain assignments.
|
||
|
* *No keywords*: Binding expressions can not contain keywords such as: `var`, `if`, and so on.
|
||
|
* *Formatters*: Angular expressions can be piped through formatters to further transform the binding value.
|
||
|
(See: Formatters)
|
||
|
|
||
|
Examples of some expressions and their behavior:
|
||
|
|
||
|
Given:
|
||
|
```
|
||
|
class Greeter {
|
||
|
name:string;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
* `name` : Will result in the value of the `name` property on the `Greeter` class.
|
||
|
* `name.length`: Will result in either the length of the `name` string or `undefined` (`null` in Dart) if `name`
|
||
|
property is `null` or `undefined`. Example of: safe dereference.
|
||
|
* `foo`: Will thrown on error because `foo` is not declared on the `Greeter` class. Example of: Must be defined
|
||
|
* `name=1`: Not allowed because fo assignment.
|
||
|
* `name; name.length`: Not allowed because of multiple statments.
|
||
|
|
||
|
|
||
|
|
||
|
### Statements
|
||
|
|
||
|
Statements can be used to bind to events only. Statements represent actions to trigger as a response to an event.
|
||
|
Examples of where statments can be used in Angular are:
|
||
|
```
|
||
|
<div (click)="statments">...</div>
|
||
|
<div on-click="statments">...</div>
|
||
|
```
|
||
|
|
||
|
Statements are similar to statments in the langugage in which you are writing your application. (i.e.
|
||
|
statments fallow JS syntax and semantics in JS and Dart syntax and semantics in Dart). Unlike statments in the
|
||
|
langugage, binding expressions behave differently in following ways:
|
||
|
|
||
|
* *Unsafe dereference*: Expressions `user.verify()` where `user` is null will throw `NullPointerException` in the
|
||
|
langugage as well as in statments. (In contrast to Safe dereference in Angular expressions.) While angular protects
|
||
|
you from null dereferencing in expressions due to lazy loading of data, no such protection is required for statments,
|
||
|
and doing so would make it harder to detect typos in statements.
|
||
|
* *Multiple statements OK*: Statements can be composed from more than one statemnet. (i.e. no `doA(); doB()`)
|
||
|
* *Assignments OK*: Event bindings can have sideeffects and hence assignments are allowed.
|
||
|
* *No keywords*: Statements can not contain keywords such as: `var`, `if`, and so on.
|
||
|
* *No Formatters*: Angular statements can not contain formatters. (Formatters are only usefull for data binding)
|
||
|
|
||
|
## Further Reading
|
||
|
|
||
|
* [Template Syntax Constraints and Reasoning](https://docs.google.com/document/d/1HHy_zPLGqJj0bHMiWPzPCxn1pO5GlOYwmv-qGgl4f_s)
|