diff options
| author | Brian Ford | 2013-10-23 14:04:47 -0700 | 
|---|---|---|
| committer | Brian Ford | 2013-10-23 14:17:27 -0700 | 
| commit | e69c2872939cda30f13a1a8390bbc61662c12d9a (patch) | |
| tree | 20b3bb618dae4b282b51097cd5defcba83c12003 /docs/content | |
| parent | 32ab648c79b4783f1cc2491919b659d17004356c (diff) | |
| download | angular.js-e69c2872939cda30f13a1a8390bbc61662c12d9a.tar.bz2 | |
docs(guide/directive,guide/compiler,): drastically improve
Diffstat (limited to 'docs/content')
| -rw-r--r-- | docs/content/error/compile/ctreq.ngdoc | 2 | ||||
| -rw-r--r-- | docs/content/error/compile/iscp.ngdoc | 3 | ||||
| -rw-r--r-- | docs/content/error/compile/nonassign.ngdoc | 4 | ||||
| -rw-r--r-- | docs/content/guide/compiler.ngdoc | 266 | ||||
| -rw-r--r-- | docs/content/guide/directive.ngdoc | 1436 | 
5 files changed, 1048 insertions, 663 deletions
| diff --git a/docs/content/error/compile/ctreq.ngdoc b/docs/content/error/compile/ctreq.ngdoc index 9f9a1ec1..e3f46532 100644 --- a/docs/content/error/compile/ctreq.ngdoc +++ b/docs/content/error/compile/ctreq.ngdoc @@ -3,7 +3,7 @@  @fullName Missing Required Controller  @description -This error occurs when {@link api/ng.$compile template compiler} tries process the directive that specifies the `require` option in a {@link guide/directive#writing-directives_directive-definition-object directive definition}, +This error occurs when {@link api/ng.$compile HTML compiler} tries to process a directive that specifies the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object `require` option} in a {@link api/ng.$compile#description_comprehensive-directive-api directive definition},  but the required directive controller is not present on the current DOM element (or its ancestor element, if `^` was specified).  To resolve this error ensure that there is no typo in the required controller name and that the required directive controller is present on the current element. diff --git a/docs/content/error/compile/iscp.ngdoc b/docs/content/error/compile/iscp.ngdoc index 0d7dbec1..0bfe216b 100644 --- a/docs/content/error/compile/iscp.ngdoc +++ b/docs/content/error/compile/iscp.ngdoc @@ -21,4 +21,5 @@ myModule.directive('directiveName', function factory() {  });  ``` -Please refer to the {@link guide/directive#writing-directives_directive-definition-object directive definition docs} to learn more about the api. +Please refer to the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object +`scope` option} of the directive definition documentation to learn more about the API. diff --git a/docs/content/error/compile/nonassign.ngdoc b/docs/content/error/compile/nonassign.ngdoc index 9f78ed3f..3870c2ed 100644 --- a/docs/content/error/compile/nonassign.ngdoc +++ b/docs/content/error/compile/nonassign.ngdoc @@ -3,7 +3,9 @@  @fullName Non-Assignable Expression  @description -This error occurs when a directive defines an isolate scope property that support two-way data-binding (using the `=` mode in the {@link guide/directive#writing-directives_directive-definition-object  directive definition}) but the directive is used with an expression that is not-assignable. +This error occurs when a directive defines an isolate scope property +(using the `=` mode in the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object +`scope` option} of a directive definition) but the directive is used with an expression that is not-assignable.  In order for the two-way data-binding to work, it must be possible to write new values back into the path defined with the expression. diff --git a/docs/content/guide/compiler.ngdoc b/docs/content/guide/compiler.ngdoc index 257722e6..a8b96aac 100644 --- a/docs/content/guide/compiler.ngdoc +++ b/docs/content/guide/compiler.ngdoc @@ -2,6 +2,15 @@  @name Developer Guide: HTML Compiler  @description +<div class="alert alert-warning"> +**Note:** this guide is targeted towards developers who are already familiar with AngularJS basics. + +If you're just getting started, we recommend the {@link tutorial/ tutorial} first. +If you just want to create custom directives, we recommend the {@link guide/directive directives guide}. +If you want a deeper look into Angular's compilation process, you're in the right place. +</div> + +  # Overview  Angular's {@link api/ng.$compile HTML compiler} allows the developer to teach the @@ -27,9 +36,9 @@ All of this compilation takes place in the web browser; no server side or pre-co  involved. -# Compiler +## Compiler -Compiler is an angular service which traverses the DOM looking for attributes. The compilation +Compiler is an Angular service which traverses the DOM looking for attributes. The compilation  process happens in two phases.    1. **Compile:** traverse the DOM and collect all of the directives. The result is a linking @@ -44,7 +53,7 @@ for each item in a collection. Having a compile and link phase improves performa  cloned template only needs to be compiled once, and then linked once for each clone instance. -# Directive +## Directive  A directive is a behavior which should be triggered when specific HTML constructs are encountered  during the compilation process. The directives can be placed in element names, attributes, class @@ -108,35 +117,260 @@ Here is a directive which makes any element draggable. Notice the `draggable` at  </example> -The presence of the `draggable` attribute on any element gives the element new behavior. The beauty of -this approach is that we have taught the browser a new trick. We have extended the vocabulary of -what the browser understands in a way which is natural to anyone who is familiar with HTML -principles. +The presence of the `draggable` attribute on any element gives the element new behavior. +We extended the vocabulary of the browser in a way which is natural to anyone who is familiar with the principles of HTML. -# Understanding View +## Understanding View -There are many templating systems out there. Most of them consume a static string template and +Most other templating systems consume a static string template and  combine it with data, resulting in a new string. The resulting text is then `innerHTML`ed into  an element.  <img src="img/One_Way_Data_Binding.png">  This means that any changes to the data need to be re-merged with the template and then -`innerHTML`ed into the DOM. Some of the issues with this approach are: reading user input and merging it with data, -clobbering user input by overwriting it, managing the whole update process, and lack of behavior -expressiveness. +`innerHTML`ed into the DOM. Some of the issues with this approach are: -Angular is different. The Angular compiler consumes the DOM with directives, not string templates. +1. reading user input and merging it with data +2. clobbering user input by overwriting it +3. managing the whole update process +4. lack of behavior expressiveness + +Angular is different. The Angular compiler consumes the DOM, not string templates.  The result is a linking function, which when combined with a scope model results in a live view. The -view and scope model bindings are transparent. No action from the developer is needed to update -the view. And because no `innerHTML` is used there are no issues of clobbering user input. +view and scope model bindings are transparent. The developer does not need to make any special calls to update +the view. And because `innerHTML` is not used, you won't accidentally clobber user input.  Furthermore, Angular directives can contain not just text bindings, but behavioral constructs as  well.  <img src="img/Two_Way_Data_Binding.png"> -The Angular approach produces a stable DOM. This means that the DOM element instance bound to a model +The Angular approach produces a stable DOM. The DOM element instance bound to a model  item instance does not change for the lifetime of the binding. This means that the code can get  hold of the elements and register event handlers and know that the reference will not be destroyed  by template data merge. + + + +## How directives are compiled + +It's important to note that Angular operates on DOM nodes rather than strings. Usually, you don't +notice this restriction because when a page loads, the web browser parses HTML into the DOM automatically. + +However it's important to keep this in mind when calling `$compile` yourself, because passing it a string +will fail. Instead, use `angular.element` to convert a string to DOM before passing elements into +Angular's `$compile` service. + +HTML compilation happens in three phases: + +  1. {@link api/ng.$compile `$compile`} traverses the DOM and matches directives. + +  If the compiler finds than an element matches a directive, then the directive is added to the list of +  directives that match the DOM element. A single element may match multiple directives. + +  2. Once all directives matching a DOM element have been identified, the compiler sorts the directives +  by their `priority`. + +  Each directive's `compile` functions are executed. Each `compile` function has a chance to +  modify the DOM. Each `compile` function returns a `link` function. These functions are composed into +  a "combined" link function, which invokes each directive's returned `link` function. + +  3. `$compile` links the template with the scope by calling the combined linking function from the previous step. +  This in turn will call the linking function of the individual directives, registering listeners on the elements +  and setting up {@link api/ng.$rootScope.Scope#methods_$watch `$watch`s} with the {@link api/ng.$rootScope.Scope `scope`} +  as each directive is configured to do. + +The result of this is a live binding between the scope and the DOM. So at this point, a change in +a model on the compiled scope will be reflected in the DOM. + +Below is the corresponding code using the `$compile` service. +This should help give you an idea of what Angular does internally. + +<pre> +  var $compile = ...; // injected into your code +  var scope = ...; + +  var html = '<div ng-bind="exp"></div>'; + +  // Step 1: parse HTML into DOM element +  var template = angular.element(html); + +  // Step 2: compile the template +  var linkFn = $compile(template); + +  // Step 3: link the compiled template with the scope. +  linkFn(scope); +</pre> + +### The difference between Compile and Link + +At this point you may wonder why the compile process has separate compile and link phases. The +short answer is that compile and link separation is needed any time a change in a model causes +a change in the **structure** of the DOM. + +It's rare for directives to have a **compile function**, since most directives are concerned with +working with a specific DOM element instance rather than changing its overall structure. + +Directives often have a **link function**. A link function allows the directive to register +listeners to the specific cloned DOM element instance as well as to copy content into the DOM +from the scope. + +<div class="alert alert-success"> +**Best Practice:** Any operation which can be shared among the instance of directives should be +moved to the compile function for performance reasons. +</div> + +#### An Example of "Compile" Versus "Link" + +To understand, let's look at a real-world example with `ngRepeat`: + +<pre> +Hello {{user}}, you have these actions: +<ul> +  <li ng-repeat="action in user.actions"> +    {{action.description}} +  </li> +</ul> +</pre> + +When the above example is compiled, the compiler visits every node and looks for directives. + +`{{user}}` matches the {@link api/ng.$interpolate interpolation directive} +and `ng-repeat` matches the {@link api/ng.directive:ngRepeat `ngRepeat` directive}. + +But {@link api/ng.directive:ngRepeat ngRepeat} has a dilemma. + +It needs to be able to clone new `<li>` elements for every `action` in `user.actions`. +This initially seems trivial, but it becomes more complicated when you consider that `user.actions` +might have items added to it later. This means that it needs to save a clean copy of the `<li>` +element for cloning purposes. + +As new `action`s are inserted, the template `<li>` element needs to be cloned and inserted into `ul`. +But cloning the `<li>` element is not enough. It also needs to compile the `<li>` so that its +directives, like `{{action.description}}`, evaluate against the right {@link api/ng.$rootScope.Scope scope}. + + +A naive approach to solving this problem would be to simply insert a copy of the `<li>` element and +then compile it. +The problem with this approach is that compiling on every `<li>` element that we clone would duplicate +a lot of the work. Specifically, we'd be traversing `<li>` each time before cloning it to find the +directives. This would cause the compilation process to be slower, in turn making applications +less responsive when inserting new nodes. + +The solution is to break the compilation process into two phases: + +the **compile phase** where all of the directives are identified and sorted by priority, +and a **linking phase** where any work which "links" a specific instance of the +{@link api/ng.$rootScope.Scope scope} and the specific instance of an `<li>` is performed. + +<div class="alert alert-warning"> +**Note:** *Link* means setting up listeners on the DOM and setting up `$watch` on the Scope to +keep the two in sync. +</div> + +{@link api/ng.directive:ngRepeat `ngRepeat`} works by preventing the compilation process from +descending into the `<li>` element so it can make a clone of the original and handle inserting +and removing DOM nodes itself. + +Instead the {@link api/ng.directive:ngRepeat `ngRepeat`} directive compiles `<li>` separately. +The result of the `<li>` element compilation is a linking function which contains all of the +directives contained in the `<li>` element, ready to be attached to a specific clone of the `<li>` +element. + +At runtime the {@link api/ng.directive:ngRepeat `ngRepeat`} watches the expression and as items +are added to the array it clones the `<li>` element, creates a new +{@link api/ng.$rootScope.Scope scope} for the cloned `<li>` element and calls the link function +on the cloned `<li>`. + + + +### Understanding How Scopes Work with Transclused Directives + +One of the most common use cases for directives is to create reusable components. + +Below is a pseudo code showing how a simplified dialog component may work. + +<pre> +<div> +  <button ng-click="show=true">show</button> + +  <dialog title="Hello {{username}}." +          visible="show" +          on-cancel="show = false" +          on-ok="show = false; doSomething()"> +     Body goes here: {{username}} is {{title}}. +  </dialog> +</div> +</pre> + +Clicking on the "show" button will open the dialog. The dialog will have a title, which is +data bound to `username`, and it will also have a body which we would like to transclude +into the dialog. + +Here is an example of what the template definition for the `dialog` widget may look like. + +<pre> +<div ng-show="visible"> +  <h3>{{title}}</h3> +  <div class="body" ng-transclude></div> +  <div class="footer"> +    <button ng-click="onOk()">Save changes</button> +    <button ng-click="onCancel()">Close</button> +  </div> +</div> +</pre> + +This will not render properly, unless we do some scope magic. + +The first issue we have to solve is that the dialog box template expects `title` to be defined, but +the place of instantiation would like to bind to `username`. Furthermore the buttons expect the +`onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the +widget. To solve the mapping issue we use the `locals` to create local variables which the template +expects as follows: + +<pre> +  scope: { +    title: '@',             // the title uses the data-binding from the parent scope +    onOk: '&',              // create a delegate onOk function +    onCancel: '&',          // create a delegate onCancel function +    visible: '='            // set up visible to accept data-binding +  } +</pre> + +Creating local properties on widget scope creates two problems: + +  1. isolation - if the user forgets to set `title` attribute of the dialog widget the dialog +     template will bind to parent scope property. This is unpredictable and undesirable. + +  2. transclusion - the transcluded DOM can see the widget locals, which may overwrite the +     properties which the transclusion needs for data-binding. In our example the `title` +     property of the widget clobbers the `title` property of the transclusion. + + +To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An +isolated scope does not prototypically inherit from the child scope, and therefore we don't have +to worry about accidentally clobbering any properties. + +However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget +isolated scope then it will not be able to bind to anything. For this reason the transcluded scope +is a child of the original scope, before the widget created an isolated scope for its local +variables. This makes the transcluded and widget isolated scope siblings. + +This may seem to be unexpected complexity, but it gives the widget user and developer the least +surprise. + +Therefore the final directive definition looks something like this: + +<pre> +transclude: true, +scope: { +    title: '@',             // the title uses the data-binding from the parent scope +    onOk: '&',              // create a delegate onOk function +    onCancel: '&',          // create a delegate onCancel function +    visible: '='            // set up visible to accept data-binding +}, +restrict: 'E', +replace: true +</pre> + diff --git a/docs/content/guide/directive.ngdoc b/docs/content/guide/directive.ngdoc index f548164f..b709b7e1 100644 --- a/docs/content/guide/directive.ngdoc +++ b/docs/content/guide/directive.ngdoc @@ -2,741 +2,889 @@  @name Directives  @description -Directives are a way to teach HTML new tricks. During DOM compilation directives are matched -against the HTML and executed. This allows directives to register behavior, or transform the DOM. - -Angular comes with a built in set of directives which are useful for building web applications but -can be extended such that HTML can be turned into a declarative domain specific language (DSL). - -# Invoking directives from HTML - -Directives have camel cased names such as `ngBind`. The directive can be invoked by translating -the camel case name into snake case with these special characters `:`, `-`, or `_`. Optionally the -directive can be prefixed with `x-`, or `data-` to make it HTML validator compliant. Here is a -list of some of the possible directive names: `ng:bind`, `ng-bind`, `ng_bind`, `x-ng-bind` and -`data-ng-bind`. - -The directives can be placed as element names, attributes, CSS class names, and inside comments. -However, most directives are restricted to attribute only. Here are some equivalent examples of -invoking `myDir`: - -<pre> -  <span my-dir="exp"></span> -  <span class="my-dir: exp;"></span> -  <my-dir></my-dir> -  <!-- directive: my-dir exp --> -</pre> - -The following demonstrates the various ways a Directive (ngBind in this case) can be referenced from within a template: - -<doc:example> -  <doc:source > -   <script> -     function Ctrl1($scope) { -       $scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)'; -     } -   </script> -   <div ng-controller="Ctrl1"> -     Hello <input ng-model='name'> <hr/> -     <span ng-bind="name"> <span ng-bind="name"></span> <br/> -     <span ng:bind="name"> <span ng:bind="name"></span> <br/> -     <span ng_bind="name"> <span ng_bind="name"></span> <br/> -     <span data-ng-bind="name"> <span data-ng-bind="name"></span> <br/> -     <span x-ng-bind="name"> <span x-ng-bind="name"></span> <br/> -   </div> -  </doc:source> -  <doc:scenario> +# Creating Custom Directives + +<div class="alert alert-warning"> +**Note:** this guide is targeted towards developers who are already familiar with AngularJS basics. +If you're just getting started, we recommend the {@link tutorial/ tutorial} first. +</div> + + +This document explains when you'd want to create your own directives in your AngularJS app, and +how to implement them. + + +## What are Directives? + +At a high level, directives are instructions for AngularJS's +{@link api/ng.$compile HTML Compiler (`$compile`)} that tell the +compiler to attach a given behavior to a DOM node when a certain marker (such as an attribute, element +name, or comment) appears in the DOM. + +The process is simple. Angular comes with a set of these directives, like `ngBind`, `ngModel`, and `ngView`. +Much like you create controllers and services, you can create your own directives for Angular to use. +Then, when Angular {@link guide/bootstrap bootstraps}, Angular's {@link guide/compiler HTML compiler} matches +directives against the HTML. This allows directives to register behavior or transform the DOM. + +<div class="alert alert-info"> +**What does it mean to "compile" an HTML template?** + +For AngularJS, "compilation" means attaching event listeners to the HTML to make it interactive. +The reason we use the term "compile" is that the recursive process of attaching this listeners +mirrors the compilation process of +{@link http://en.wikipedia.org/wiki/Compiled_languages compiled programming languages}. +</div> + + +## Matching Directives + +Before we can write a directive, we need to know how Angular's {@link guide/compiler HTML compiler} +determines when to use a given directive. + +In the following example, we say that the `<input>` element **matches** the `ngModel` directive. + +```javascript +<input ng-model="foo"> +``` + +The following also **matches** `ngModel`: + +```javascript +<input data-ng:model="foo"> +``` + +Angular **normalizes** an element's tag and attribute name to determine which elements match which directives. +We typically refer to directives by their camel-case **normalized** name (e.g. `ngModel`). +However directives are typically invoked by adding dash-delimited attribute names to your HTML (e.g. `ng-model`). + +The **normalization** process is as follows: + +1. Strip `x-` and `data-` from the front of the element/attributes. +2. Convert the `:`, `-`, or `_`-delimited name to `camelCase`. + +Here are some equivalent examples of elements that match `ngBind`: + +<example module="docsBindExample"> +  <file name="script.js"> +    angular.module('docsBindExample', []) +      .controller('Ctrl1', function Ctrl1($scope) { +        $scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)'; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl1"> +      Hello <input ng-model='name'> <hr/> +      <span ng-bind="name"></span> <br/> +      <span ng:bind="name"></span> <br/> +      <span ng_bind="name"></span> <br/> +      <span data-ng-bind="name"></span> <br/> +      <span x-ng-bind="name"></span> <br/> +    </div> +  </file> +  <file name="scenario.js">      it('should show off bindings', function() {        expect(element('div[ng-controller="Ctrl1"] span[ng-bind]').text())          .toBe('Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)');      }); -  </doc:scenario> -</doc:example> +  </file> +</example> -# Text and attribute bindings +<div class="alert alert-success"> +**Best Practice:** Prefer using the dash-delimited format (e.g. `ng-bind` for `ngBind`). +If you want to use an HTML validating tool, you can instead use the `data`-prefixed version (e.g. `data-ng-bind` for `ngBind`). +The other forms shown above are accepted for legacy reasons but we advise you to avoid them. +</div> -During the compilation process the {@link api/ng.$compile compiler} matches text and -attributes using the {@link api/ng.$interpolate $interpolate} service to see if they -contain embedded expressions. These expressions are registered as {@link -api/ng.$rootScope.Scope#methods_$watch watches} which will be processed as part of the normal {@link -api/ng.$rootScope.Scope#methods_$digest digest} cycle. An example of interpolation is shown -here: +`$compile` can match directives based on element names, attributes, class names, as well as comments. -<pre> -<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a> -</pre> +All of the Angular-provided directives match attribute name, tag name, comments, or class name. +The following demonstrates the various ways a directive (`ngBind` in this case) can be referenced from within a template: +```html +<my-dir></my-dir> +<span my-dir="exp"></span> +<!-- directive: my-dir exp --> +<span class="my-dir: exp;"></span> +``` -# ngAttr attribute bindings +<div class="alert alert-success"> +**Best Practice:** Prefer using directives via tag name and attributes over comment and class names. +Doing so generally makes it easier to determine what directives a given element matches. +</div> -If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized prefix: 'ng-attr-', -'ng:attr-') then, during the compilation process, the prefix will be removed and the binding will be applied -to an unprefixed attribute. This allows binding to attributes that would otherwise be eagerly -processed by browsers in their uncompiled form (e.g. `img[src]` or svg's `circle[cx]` attributes). - -For example, assume you have a model property cx=5 and the following template: - -    <svg> -      <circle ng-attr-cx="{{cx}}"></circle> -    </svg> - -The following DOM will be rendered as a result: - -    <svg> -      <circle cx="5"></circle> -    </svg> - -If you were to bind `{{cx}}` directly to the `cx` attribute, you'd get the following error: -`Error: Invalid value for attribute cx="{{cx}}"`. With `ng-attr-cx` you can work around this -problem. +<div class="alert alert-success"> +**Best Practice:** Comment directives were commonly used in places where the DOM API limits (e.g. inside `<table>` elements) +to create directives that spanned multiple elements. AngularJS 1.2 introduces +{@link api/ng.directive:ngRepeat `ng-repeat-start` and `ng-repeat-end`} as a better solution to this problem. +Developers are encouraged to use this over custom comment directives when possible. +</div> -# Compilation process, and directive matching -Compilation of HTML happens in three phases: +### Text and attribute bindings -  1. The HTML is parsed into DOM using the standard browser API. This is important to -  realize because the templates must be parsable HTML. This is in contrast to most templating -  systems that operate on strings, rather than on DOM elements. +During the compilation process the {@link api/ng.$compile compiler} matches text and attributes using the +{@link api/ng.$interpolate $interpolate} service to see if they contain embedded expressions. These expressions +are registered as {@link api/ng.$rootScope.Scope#methods_$watch watches} and will update as part of normal {@link +api/ng.$rootScope.Scope#methods_$digest digest} cycle. An example of interpolation is shown below: -  2. The compilation of the DOM is performed by the {@link api/ng.$compile -  $compile()} method. The method traverses the DOM and matches the directives. If a match is found -  it is added to the list of directives associated with the given DOM element. Once all directives -  for a given DOM element have been identified they are sorted by priority and their `compile()` -  functions are executed. The directive's compile function has a chance to modify the DOM structure -  and is responsible for producing a `link()` function. The {@link -  api/ng.$compile $compile()} method returns a combined linking function, which is a -  collection of all of the linking functions returned from the individual directive compile -  functions. +```html +<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a> +``` -  3. Link the template with a scope by calling the linking function returned from the previous step. -  This in turn will call the linking function of the individual directives allowing them to -  register any listeners on the elements and set up any {@link -  api/ng.$rootScope.Scope#methods_$watch watches} with the {@link -  api/ng.$rootScope.Scope scope}. The result is a live binding between the -  scope and the DOM (i.e., a change in the scope is reflected in the DOM). -<pre> -  var $compile = ...; // injected into your code -  var scope = ...; +### `ngAttr` attribute bindings -  var html = '<div ng-bind="exp"></div>'; +Web browsers are sometimes picky about what values they consider valid for attributes. -  // Step 1: parse HTML into DOM element -  var template = angular.element(html); +For example, considering this template: -  // Step 2: compile the template -  var linkFn = $compile(template); +```html +<svg> +  <circle cx="{{cx}}"></circle> +</svg> +``` -  // Step 3: link the compiled template with the scope. -  linkFn(scope); -</pre> +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}}"`. -## Reasons behind the compile/link separation +With `ng-attr-cx` you can work around this problem. -At this point you may wonder why the compile process is broken down into a compile and link phase. -To understand this, let's look at a real world example with a repeater: +If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`) +then during the binding 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). -<pre> -  Hello {{user}}, you have these actions: -  <ul> -    <li ng-repeat="action in user.actions"> -      {{action.description}} -    </li> -  </ul> -</pre> +For example, we could fix the example above by instead writing: -Compile and link separation is needed any time a change in the model causes -a change in the DOM structure, such as in repeaters. +```html +<svg> +  <circle ng-attr-cx="{{cx}}"></circle> +</svg> +``` -When the above example is compiled, the compiler visits every node and looks for directives. The -`{{user}}` portion is an example of an {@link api/ng.$interpolate interpolation} directive. {@link -api/ng.directive:ngRepeat ngRepeat} is another directive. The {@link -api/ng.directive:ngRepeat ngRepeat} has a dilemma. It needs to be -able to quickly stamp out new `li`s for every `action` in `user.actions`. This means that it needs -to save a clean copy of the `li` element for cloning purposes. As new `action`s are inserted, -the template `li` element needs to be cloned and inserted into `ul`. But cloning the `li` element -is not enough. It also needs to compile the `li` so that its directives (such as -`{{action.description}}`) evaluate against the right {@link api/ng.$rootScope.Scope -scope}. How should it do this? A naive approach would be to simply insert a copy of the `li` element and then compile it. -But compiling on every `li` element clone would be slow, since the compilation process requires that we -traverse the DOM tree and look for directives and execute them. If we performed the compilation process inside a -repeater which needs to unroll 100 items we would quickly run into performance problems. -The solution is to break the compilation process into two phases: the compile phase (where all of -the directives are identified and sorted by priority), and a linking phase (where any work which -links a specific instance of the {@link api/ng.$rootScope.Scope scope} and the specific -instance of an `li` is performed). - -{@link api/ng.directive:ngRepeat ngRepeat} works by preventing the -compilation process from descending into the `li` element. Instead the {@link -api/ng.directive:ngRepeat ngRepeat} directive compiles `li` -separately. The result of the `li` element compilation is a linking function which contains all -of the directives contained in the `li` element, ready to be attached to a specific clone of the `li` -element. At runtime, the {@link api/ng.directive:ngRepeat ngRepeat} -watches the expression. As items are added to the array it clones the `li` element, creates a -new {@link api/ng.$rootScope.Scope scope} for the cloned `li` element, and calls the -link function on the cloned `li`. - -Summary: - -  * *compile function* - The compile function is relatively rare in directives, since most -    directives are concerned with working with a specific DOM element instance rather than -    transforming the template's DOM element. Any operation which can be shared among all instances of a -    directive should be moved to that directive's compile function for performance reasons. - -  * *link function* - It is rare for the directive to not have a link function. A link function -    allows the directive to register listeners on the specific cloned DOM element instance as well -    as to copy content into the DOM from the scope. - - -# Writing directives (short version) +## Creating Directives -In this example we will build a directive that displays the current time. +First let's talk about the API for registering directives. Much like controllers, directives are registered on +modules. To register a controller, you use the `module.directive` API. `module.directive` takes the +{@link guide/directive#creating-custom-directives_matching-directives normalized} directive name followed +by a **factory function.** This factory function should return +an object with the different options to tell `$compile` how the directive should behave when matched. -<doc:example module="time"> -  <doc:source> -   <script> -     function Ctrl2($scope) { -       $scope.format = 'M/d/yy h:mm:ss a'; -     } - -     angular.module('time', []) -       // Register the 'myCurrentTime' directive factory method. -       // We inject $timeout and dateFilter service since the factory method is DI. -       .directive('myCurrentTime', function($timeout, dateFilter) { -         // return the directive link function. (compile function not needed) -         return function(scope, element, attrs) { -           var format,  // date format -               timeoutId; // timeoutId, so that we can cancel the time updates - -           // used to update the UI -           function updateTime() { -             element.text(dateFilter(new Date(), format)); -           } - -           // watch the expression, and update the UI on change. -           scope.$watch(attrs.myCurrentTime, function(value) { -             format = value; -             updateTime(); -           }); - -           // schedule update in one second -           function updateLater() { -             // save the timeoutId for canceling -             timeoutId = $timeout(function() { -               updateTime(); // update DOM -               updateLater(); // schedule another update -             }, 1000); -           } - -           // listen on DOM destroy (removal) event, and cancel the next UI update -           // to prevent updating time after the DOM element was removed. -           element.on('$destroy', function() { -             $timeout.cancel(timeoutId); -           }); - -           updateLater(); // kick off the UI update process. -         } -       }); -   </script> -   <div ng-controller="Ctrl2"> -     Date format: <input ng-model="format"> <hr/> -     Current time is: <span my-current-time="format"></span> -   </div> -  </doc:source> -</doc:example> - - -# Writing directives (long version) - -There are different ways to declare a directive. The difference resides in the return -value of the factory function. You can either return a Directive Definition Object -(see below) that defines the directive properties, or just the postLink function -of such an object (all other properties will have the default values). - -Here's an example directive declared with a Directive Definition Object: - -<pre> -  var myModule = angular.module(...); - -  myModule.directive('directiveName', function factory(injectables) { -    var directiveDefinitionObject = { -      priority: 0, -      template: '<div></div>', // or // function(tElement, tAttrs) { ... }, -      // or -      // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, -      replace: false, -      transclude: false, -      restrict: 'A', -      scope: false, -      controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, -      require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], -      compile: function compile(tElement, tAttrs, transclude) { -        return { -          pre: function preLink(scope, iElement, iAttrs, controller) { ... }, -          post: function postLink(scope, iElement, iAttrs, controller) { ... } -        } -	      // or -	      // return function postLink( ... ) { ... } -      }, -      // or -      // link: { -      //  pre: function preLink(scope, iElement, iAttrs, controller) { ... }, -      //  post: function postLink(scope, iElement, iAttrs, controller) { ... } -      // } -      // or -      // link: function postLink( ... ) { ... } -    }; -    return directiveDefinitionObject; -  }); -</pre> - -In most cases you will not need such fine control, so the above can be simplified. You could still -return a Directive Definition Object, but only set the 'link' function property of the Object -and rely on the default values for other properties. - -Therefore the above could be simplified as: - -<pre> -  var myModule = angular.module(...); - -  myModule.directive('directiveName', function factory(injectables) { -    var directiveDefinitionObject = { -      link: function postLink(scope, iElement, iAttrs) { ... } -    }; -    return directiveDefinitionObject; -    // or -    // return function postLink(scope, iElement, iAttrs) { ... } -  }); -</pre> - - -## Factory method - -The factory method is responsible for creating the directive. It is invoked only once, when the + +The factory function is invoked only once when the  {@link api/ng.$compile compiler} matches the directive for the first time. You can -perform any initialization work here. The method is invoked using the {@link +perform any initialization work here. The function is invoked using {@link  api/AUTO.$injector#methods_invoke $injector.invoke} which -makes it injectable (subject to all of the rules of the injection annotation). - -## Directive Definition Object - -The directive definition object provides instructions to the {@link api/ng.$compile -compiler}. The attributes are: - -  * `name` - Name of the current scope. This attribute is optional. The default value is the name given at registration. - -  * `priority` - When there are multiple directives defined on a single DOM element, sometimes it -    is necessary to specify the order in which the directives are applied. The `priority` is used -    to sort the directives before their `compile` functions get called. Priority is defined as a -    number. Directives with greater numerical `priority` are compiled first. The order of directives with -    the same priority is undefined. The default priority is `0`. - -  * `terminal` - If set to true, the current `priority` will be the last set of directives -    executed (any directives at the current priority will still execute -    as the order of execution on same `priority` is undefined). - -  * `scope` - If set to: - -    * `true` - a new scope will be created for this directive. If multiple directives on the -      same element request a new scope, only one new scope is created. The new scope rule does not -      apply for the root of the template since the root of the template always gets a new scope. - -    * `{}` (object hash) - a new 'isolate' scope is created. The 'isolate' scope differs from -      normal scope in that it does not prototypically inherit from the parent scope. This is useful -      when creating reusable components, which should not accidentally read or modify data in the -      parent scope. <br/> -      The 'isolate' scope takes an object hash which defines a set of local scope properties -      derived from the parent scope. These local properties are useful for aliasing values for -      templates. There are three possible binding strategies for passing data to and from the parent scope: - -      * `@` or `@attr` - bind a local scope property to the value of a DOM attribute. The result is -        always a string since DOM attributes are strings. If no `attr` name is specified then the -        attribute name is assumed to be the same as the local name. -        Given `<widget my-attr="hello {{name}}">` and widget definition -        of `scope: { localName:'@myAttr' }`, then the widget scope property `localName` will reflect -        the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the -        `localName` property on the widget scope. The `name` is read from the parent scope (not -        component scope). - -      * `=` or `=attr` - set up bi-directional binding between a local scope property and the -        parent scope property with the name given as the value of the `attr` attribute. If no `attr` -        name is specified then the attribute name is assumed to be the same as the local name. -        Given `<widget my-attr="parentModel">` and widget definition of -        `scope: { localModel:'=myAttr' }`, then the widget scope property `localModel` will reflect the -        value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected -        in `localModel` and vice-versa. If the parent scope property doesn't exist, -        a NON_ASSIGNABLE_MODEL_EXPRESSION exception will be thrown. You can avoid this behavior by -        using `=?` or `=?attr`, which marks the property as optional. - -      * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. -        If no `attr` name is specified then the attribute name is assumed to be the same as the -        local name. Given `<widget my-attr="count = count + value">` and widget definition of -        `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to -        a function wrapper for the `count = count + value` expression. Often it's desirable to -        pass data from the isolated scope via an expression and to the parent scope. This can be -        done by passing a map of local variable names and values into the expression wrapper function. -        For example, if the expression is `increment(amount)` then we can specify the amount value -        by calling the `localFn` as `localFn({amount: 22})`. - -  * `controller` - Controller constructor function. The controller is instantiated before the -    pre-linking phase and it is shared with other directives (see the -    `require` attribute). This allows the directives to communicate with each other and augment -    each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: - -    * `$scope` - Current scope associated with the element -    * `$element` - Current element -    * `$attrs` - Current attributes object for the element -    * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: -      `function(cloneLinkingFn)`. - -  * `require` - Require another directive and inject its controller as the fourth argument to the linking function. The -    `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the injected -    argument will be an array in corresponding order. If no such directive can be -    found or if the directive does not have a controller, then an error is raised. The name can be prefixed with: - -    * (no prefix) - Locate the required controller on the current element. -    * `?` - Attempt to locate the required controller, or return `null` if not found. -    * `^` - Locate the required controller by searching the element's parents. -    * `?^` - Attempt to locate the required controller by searching the element's parents, or return `null` if not found. - -  * `controllerAs` - Creates a controller alias in the directive scope so it -    can be referenced in the directive template. The directive must define a scope for this -    configuration to be used. This attribute is useful when the directive is used as a component. - -  * `restrict` - String of subset of `EACM` which restricts the directive to a specific directive -    declaration style. Defaults to 'A'. - -    * `E` - Element name: `<my-directive></my-directive>` -    * `A` - Attribute (default): `<div my-directive="exp"></div>` -    * `C` - Class: `<div class="my-directive: exp;"></div>` -    * `M` - Comment: `<!-- directive: my-directive exp -->` +makes it injectable just like a controller. -  * `template` - replace the current element with the contents of the given HTML. The replacement process -    migrates all of the attributes / classes from the old element to the new one. See the -    {@link guide/directive#creating-components Creating Components} section below for more information. +<div class="alert alert-success"> +**Best Practice:** Prefer using the definition object over returning a function. +</div> -    You can specify `template` as a string representing the template or as a function which takes -    two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and -    returns a string value representing the template. -  * `templateUrl` - Same as `template` but the template is loaded from the specified URL. Because -    the template loading is asynchronous, the compilation/linking is suspended until the template -    is loaded. +We'll go over a few common examples of directives, then dive deep into the different options +and compilation process. -    You can specify `templateUrl` as a string representing the URL or as a function which takes two -    arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns -    a string value representing the url.  In either case, the template URL is passed through {@link -    api/ng.$sce#methods_getTrustedResourceUrl $sce.getTrustedResourceUrl}. +<div class="alert alert-success"> +**Best Practice:** In order to avoid collisions with some future standard, it's best to prefix your own +directive names. For instance, if you created a `<carousel>` directive, it would be problematic if HTML7 +introduced the same element. A two or three letter prefix (e.g. `btfCarousel`) works well. Similarly, do +not prefix your own directives with `ng` or they might conflict with directives included in a future +version of Angular. +</div> -  * `replace` - specify where the template should be inserted. Defaults to `false`. +For the following examples, we'll use the prefix `my` (e.g. `myCustomer`). -    * `true` - the template will replace the current element. -    * `false` - the template will replace the contents of the current element. +### Template-expanding directive -  * `transclude` - compile the content of the element and make it available to the directive. -    Typically used with {@link api/ng.directive:ngTransclude -    ngTransclude}. The advantage of transclusion is that the linking function receives a -    transclusion function which is pre-bound to the correct scope. In a typical setup the widget -    creates an `isolate` scope, but the transclusion is a sibling (rather than a child) of the `isolate` -    scope. This makes it possible for the widget to have private state, and the transclusion to -    be bound to the parent (pre-`isolate`) scope. +Let's say you have a chunk of your template that represents a customer's information. This template is repeated +many times in your code. When you change it in one place, you have to change it in several others. This is a +good opportunity to use a directive to simplify your template. -    * `true` - transclude the content of the directive. -    * `'element'` - transclude the whole element including any directives defined at lower priority. +Let's create a directive that simply replaces its contents with a static template: +<example module="docsSimpleDirective"> +  <file name="script.js"> +    angular.module('docsSimpleDirective', []) +      .controller('Ctrl', function($scope) { +        $scope.customer = { +          name: 'Naomi', +          address: '1600 Amphitheatre' +        }; +      }) +      .directive('myCustomer', function() { +        return { +          template: 'Name: {{customer.name}} Address: {{customer.address}}' +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl"> +      <div my-customer></div> +    </div> +  </file> +</example> + +Notice that we have bindings in this directive. After `$compile` compiles and links `<div my-customer></div>`, +it will try to match directives on the element's children. This means you can compose directives of other directives. +We'll see how to do that in {@link +guide/directive#creating-custom-directives_demo_creating-directives-that-communicate an example} below. + +In the example above we in-lined the value of the `template` option, but this will become annoying as the size +of your template grows. + +<div class="alert alert-success"> +**Best Practice:** Unless your template is very small, it's typically better to break it apart into its own +HTML file and load it with the `templateUrl` option. +</div> + +If you are familiar with `ngInclude`, `templateUrl` works just like it. Here's the same example using `templateUrl` +instead: + +<example module="docsTemplateUrlDirective"> +  <file name="script.js"> +    angular.module('docsTemplateUrlDirective', []) +      .controller('Ctrl', function($scope) { +        $scope.customer = { +          name: 'Naomi', +          address: '1600 Amphitheatre' +        }; +      }) +      .directive('myCustomer', function() { +        return { +          templateUrl: 'my-customer.html' +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl"> +      <div my-customer></div> +    </div> +  </file> +  <file name="my-customer.html"> +    Name: {{customer.name}} Address: {{customer.address}} +  </file> +</example> + +Great! But what if we wanted to have our directive match the tag name `<my-customer>` instead? +If we simply put a `<my-customer>` element into the HMTL, it doesn't work. + +<div class="alert alert-waring"> +**Note:** When you create a directive, it is restricted to attribute only by default. In order to create +directives that are triggered by element name, you need to use the `restrict` option. +</div> + +The `restrict` option is typically set to: + +* `'A'` - only matches attribute name +* `'E'` - only matches element name +* `'AE'` - matches either attribute or element name + +Let's change our directive to use `restrict: 'E'`: + +<example module="docsRestrictDirective"> +  <file name="script.js"> +    angular.module('docsRestrictDirective', []) +      .controller('Ctrl', function($scope) { +        $scope.customer = { +          name: 'Naomi', +          address: '1600 Amphitheatre' +        }; +      }) +      .directive('myCustomer', function() { +        return { +          restrict: 'E', +          templateUrl: 'my-customer.html' +        }; +      }); +  </file> + +  <file name="index.html"> +    <div ng-controller="Ctrl"> +      <my-customer></my-customer> +    </div> +  </file> -  * `compile`: This is the compile function described in the section below. +  <file name="my-customer.html"> +    Name: {{customer.name}} Address: {{customer.address}} +  </file> +</example> -  * `link`: This is the link function described in the section below. This property is used only -    if the `compile` property is not defined. +For more on the {@link api/ng.$compile#description_comprehensive-directive-api_directive-definition-object +`restrict`, see the API docs}. -## Compile function +<div class="alert alert-info"> +**When should I use an attribute versus an element?** -<pre> -  function compile(tElement, tAttrs, transclude) { ... } -</pre> +Use an element when you are creating a component that is in control of the template. The common case for this +is when you are creating a Domain-Specific Language for parts of your template. -The compile function deals with transforming the template DOM. Since most directives do not transform -the template, it is not used often. Examples that require compile functions are -directives that transform template DOM (such as {@link -api/ng.directive:ngRepeat ngRepeat}), or that load the contents -asynchronously (such as {@link api/ngRoute.directive:ngView ngView}). The -compile function takes the following arguments: +Use an attribute when you are decorating an existing element with new functionality. +</div> -  * `tElement` - template element - The element where the directive has been declared. It is -    safe to do template transformation on the element and child elements only. +Using an element for the `myCustomer` directive is clearly the right choice because you're not decorating an element +with some "customer" behavior; you're defining the core behavior of the element as a customer component. -  * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared -    between all directive compile functions. See {@link -    guide/directive#writing-directives_attributes Attributes}. -  * `transclude` - A transclude linking function: `function(scope, cloneLinkingFn)`. -NOTE: The template instance and the link instance may not be the same objects if the template has -been cloned. For this reason it is not safe to do anything in the compile function other than DOM -transformation that applies to all DOM clones. Specifically, DOM listener registration should be -done in a linking function rather than in a compile function. +### Isolating the Scope of a Directive -A compile function can have a return value which can be either a function or an object. Returning a -(post-link) function is equivalent to registering the linking function via the 'link` property of -the config object when the compile function is empty. Returning an object with function(s) registered -via `pre` and `post` properties allows you to control when a linking function should be called during -the linking phase. See info about pre-linking and post-linking functions below. +Our `myCustomer` directive above is great, but it has a fatal flaw. We can only use it once within a given scope. +In its current implementation, we'd need to create a different controller each time In order to re-use such a directive: -## Linking function +<example module="docsScopeProblemExample"> +  <file name="script.js"> +    angular.module('docsScopeProblemExample', []) +      .controller('NaomiCtrl', function($scope) { +        $scope.customer = { +          name: 'Naomi', +          address: '1600 Amphitheatre' +        }; +      }) +      .controller('IgorCtrl', function($scope) { +        $scope.customer = { +          name: 'Igor', +          address: '123 Somewhere' +        }; +      }) +      .directive('myCustomer', function() { +        return { +          restrict: 'E', +          templateUrl: 'my-customer.html' +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="NaomiCtrl"> +      <my-customer></my-customer> +    </div> +    <hr> +    <div ng-controller="IgorCtrl"> +      <my-customer></my-customer> +    </div> +  </file> +  <file name="my-customer.html"> +    Name: {{customer.name}} Address: {{customer.address}} +  </file> +</example> + +This is clearly not a great solution. + +What we want to be able to do is separate the scope inside a directive from the scope +outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an +**isolate scope**. To do this, we can use a directive's `scope` option: + +<example module="docsIsolateScopeDirective"> +  <file name="script.js"> +    angular.module('docsIsolateScopeDirective', []) +      .controller('Ctrl', function($scope) { +        $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' }; +        $scope.igor = { name: 'Igor', address: '123 Somewhere' }; +      }) +      .directive('myCustomer', function() { +        return { +          restrict: 'E', +          scope: { +            customer: '=customer' +          }, +          templateUrl: 'my-customer.html' +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl"> +      <my-customer customer="naomi"></my-customer> +      <hr> +      <my-customer customer="igor"></my-customer> +    </div> +  </file> +  <file name="my-customer.html"> +    Name: {{customer.name}} Address: {{customer.address}} +  </file> +</example> -<pre> -  function link(scope, iElement, iAttrs, controller) { ... } -</pre> +Looking at `index.html`, the first `<my-customer>` element binds the inner scope's `customer` to `naomi`, +which we have exposed on our controller's scope. The second binds `customer` to `igor`. -The link function is responsible for registering DOM listeners as well as updating the DOM. It is -executed after the template has been cloned. This is where most of the directive logic will be -put. +Let's take a closer look at the scope option: -  * `scope` - {@link api/ng.$rootScope.Scope Scope} - The scope to be used by the -    directive for registering {@link api/ng.$rootScope.Scope#methods_$watch watches}. +```javascript +//... +scope: { +  customer: '=customer' +}, +//... +``` -  * `iElement` - instance element - The element where the directive is to be used. It is safe to -    manipulate the children of the element only in `postLink` function since the children have -    already been linked. +The property name (`customer`) corresponds to the variable name of the `myCustomer` directive's isolated scope. +The value of the property (`=customer`) tells `$compile` to bind to the `customer` attribute. -  * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared -    between all directive linking functions. See {@link -    guide/directive#writing-directives_attributes Attributes}. +<div class="alert alert-warning"> +**Note:** These `=attr` attributes in the `scope` option of directives are normalized just like directive names. +To bind to the attribute in `<div bind-to-this="thing">`, you'd specify a binding of `=bindToThis`. +</div> -  * `controller` - a controller instance - A controller instance if at least one directive on the -    element defines a controller. The controller is shared among all the directives, which allows -    the directives to use the controllers as a communication channel. +For cases where the attribute name is the same as the value you want to bind to inside +the directive's scope, you can use this shorthand syntax: +```javascript +//... +scope: { +            // same as '=customer' +  customer: '=' +}, +//... +``` +Besides making it possible to bind different data to the scope inside a directive, using an isolated scope has another +effect. -### Pre-linking function +We can show this by adding another property, `vojta`, to our scope and trying to access it +from within our directive's template: -Executed before the child elements are linked. Not safe to do DOM transformation since the -compiler linking function will fail to locate the correct elements for linking. +<example module="docsIsolationExample"> +  <file name="script.js"> +    angular.module('docsIsolationExample', []) +      .controller('Ctrl', function($scope) { +        $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' }; + +        $scope.vojta = { name: 'Vojta', address: '3456 Somewhere Else' }; +      }) +      .directive('myCustomer', function() { +        return { +          restrict: 'E', +          scope: { +            customer: '=customer' +          }, +          templateUrl: 'my-customer-plus-vojta.html' +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl"> +      <my-customer customer="naomi"></my-customer> +    </div> +  </file> +  <file name="my-customer-plus-vojta.html"> +    Name: {{customer.name}} Address: {{customer.address}} +    <hr> +    Name: {{vojta.name}} Address: {{vojta.address}} +  </file> +</example> -### Post-linking function +Notice that `{{vojta.name}}` and `{{vojta.address}}` are empty, meaning they are undefined. +Although we defined `vojta` in the controller, it's not available within the directive. -Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. +As the name suggests, the **isolate scope** of the directive isolates everything except models that +you've explicitly added to the `scope: {}` hash object. This is helpful when building reusable +components because it prevents a component from changing your model state except for the models +that you explicitly pass in. -<a name="Attributes"></a> -## Attributes +<div class="alert alert-warning"> +**Note:** Normally, a scope prototypically inherits from its parent. An isolated scope does not. +</div> -The {@link api/ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the -link() or compile() functions - is a way of accessing: +<div class="alert alert-success"> +**Best Practice:** Use the `scope` option to create isolate scopes when making components that you +want to reuse throughout your app. +</div> -  * *normalized attribute names:* Since a directive such as 'ngBind' can be expressed in many ways -    such as 'ng:bind', or 'x-ng-bind', the attributes object allows for normalized access to -    the attributes. -  * *directive inter-communication:* All directives share the same instance of the attributes -    object which allows the directives to use the attributes object as inter directive -    communication. +### Creating a Directive that Manipulates the DOM -  * *supports interpolation:* Interpolation attributes are assigned to the attribute object -    allowing other directives to read the interpolated value. +In this example we will build a directive that displays the current time. +Once a second, it updates the DOM to reflect the current time. + +Directives that want to modify the DOM typically use the `link` option. +`link` takes a function with the following signature, `function link(scope, element, attrs) { ... }` where: + +* `scope` is an Angular scope object. +* `element` is the jqLite-wrapped element that this directive matches. +* `attrs` is an object with the normalized attribute names and their corresponding values. + +In our `link` function, we want to update the displayed time once a second, or whenever a user +changes the time formatting string that our directive binds to. We also want to remove the timeout +if the directive is deleted so we don't introduce a memory leak. + +<example module="docsTimeDirective"> +  <file name="script.js"> +    angular.module('docsTimeDirective', []) +      .controller('Ctrl2', function($scope) { +        $scope.format = 'M/d/yy h:mm:ss a'; +      }) +      .directive('myCurrentTime', function($timeout, dateFilter) { + +        function link(scope, element, attrs) { +          var format, +              timeoutId; + +          function updateTime() { +            element.text(dateFilter(new Date(), format)); +          } + +          scope.$watch(attrs.myCurrentTime, function(value) { +            format = value; +            updateTime(); +          }); + +          function scheduleUpdate() { +            // save the timeoutId for canceling +            timeoutId = $timeout(function() { +              updateTime(); // update DOM +              scheduleUpdate(); // schedule the next update +            }, 1000); +          } + +          element.on('$destroy', function() { +            $timeout.cancel(timeoutId); +          }); + +          // start the UI update process. +          scheduleUpdate(); +        } -  * *observing interpolated attributes:* Use `$observe` to observe the value changes of attributes -    that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also -    the only way to easily get the actual value because during the linking phase the interpolation -    hasn't been evaluated yet and so the value is at this time set to `undefined`. +        return { +          link: link +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl2"> +      Date format: <input ng-model="format"> <hr/> +      Current time is: <span my-current-time="format"></span> +    </div> +  </file> +</example> -<pre> -function linkingFn(scope, elm, attrs, ctrl) { -  // get the attribute value -  console.log(attrs.ngModel); +There are a couple of things to note here. +Just like the `module.controller` API, the function argument in `module.directive` is dependency injected. +Because of this, we can use `$timeout` and `dateFilter` inside our directive's `link` function. -  // change the attribute -  attrs.$set('ngModel', 'new value'); +We register an event `element.on('$destroy', ...)`. What fires this `$destroy` event? -  // observe changes to interpolated attribute -  attrs.$observe('ngModel', function(value) { -    console.log('ngModel has changed value to ' + value); -  }); -} -</pre> +There are a few special events that AngularJS emits. When a DOM node that has been compiled +with Angular's compiler is destroyed, it emits a `$destroy` event. Similarly, when an AngularJS +scope is destroyed, it broadcasts a `$destroy` event to listening scopes. +By listening to this event, you can remove event listeners that might cause memory leaks. +Listeners registered to scopes and elements are automatically cleaned up when they are destroyed, +but if you registered a listener on a service, or registered a listener on a DOM node that isn't +being deleted, you'll have to clean it up yourself or you risk introducing a memory leak. -# Understanding Transclusion and Scopes +<div class="alert alert-success"> +**Best Practice:** Directives should clean up after themselves. You can use `element.on('$destroy', ...)` +or `scope.$on('$destroy', ...)` to run a clean-up function when the directive is removed. +</div> -It is often desirable to have reusable components. Below is pseudo-code showing how a simplified -dialog component may work. -<pre> -  <div> -    <button ng-click="show=true">show</button> -    <dialog title="Hello {{username}}." -            visible="show" -            on-cancel="show = false" -            on-ok="show = false; doSomething()"> -       Body goes here: {{username}} is {{title}}. -    </dialog> -  </div> -</pre> +### Creating a Directive that Wraps Other Elements -Clicking on the "show" button will open the dialog. The dialog will have a title, which is -data bound to `username`, and it will also have a body which we would like to transclude -into the dialog. +We've seen that you can pass in models to a directive using the isolate scope, but sometimes +it's desirable to be able to pass in an entire template rather than a string or an object. +Let's say that we want to create a "dialog box" component. The dialog box should be able to +wrap any arbitrary content. -Here is an example of what the template definition for the `dialog` widget may look like. +To do this, we need to use the `transclude` option. -<pre> -  <div ng-show="visible"> -    <h3>{{title}}</h3> -    <div class="body" ng-transclude></div> -    <div class="footer"> -      <button ng-click="onOk()">Save changes</button> -      <button ng-click="onCancel()">Close</button> +<example module="docsTransclusionDirective"> +  <file name="script.js"> +    angular.module('docsTransclusionDirective', []) +      .controller('Ctrl', function($scope) { +        $scope.name = 'Tobias'; +      }) +      .directive('myDialog', function() { +        return { +          restrict: 'E', +          transclude: true, +          templateUrl: 'my-dialog.html' +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl"> +      <my-dialog>Check out the contents, {{name}}!</my-dialog> +    </div> +  </file> +  <file name="my-dialog.html"> +    <div class="alert" ng-transclude> +    </div> +  </file> +</example> + +What does this `transclude` option do, exactly? `transclude` makes the contents of a directive with this +option have access to the scope **outside** of the directive rather than inside. + +To illustrate this, see the example below. Notice that we've added a `link` function in `script.js` that +redefines `name` as `Jeff`. What do you think the `{{name}}` binding will resolve to now? + +<example module="docsTransclusionExample"> +  <file name="script.js"> +    angular.module('docsTransclusionExample', []) +      .controller('Ctrl', function($scope) { +        $scope.name = 'Tobias'; +      }) +      .directive('myDialog', function() { +        return { +          restrict: 'E', +          transclude: true, +          templateUrl: 'my-dialog.html', +          link: function (element, scope) { +            scope.name = 'Jeff'; +          } +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl"> +      <my-dialog>Check out the contents, {{name}}!</my-dialog>      </div> -  </div> -</pre> +  </file> +  <file name="my-dialog.html"> +    <div class="alert" ng-transclude> +    </div> +  </file> +</example> + +Ordinarily, we would expect that `{{name}}` would be `Jeff`. However, we see in this example that +the `{{name}}` binding is still `Tobias`. + +The `transclude` option changes the way scopes are nested. It makes it so that the **contents** of a +transcluded directive have whatever scope is outside the directive, rather than whatever scope is on +the inside. In doing so, it gives the contents access to the outside scope. + +This behavior makes sense for a directive that wraps some content, because otherwise you'd have to +pass in each model you wanted to use separately. If you have to pass in each model that you want to +use, then you can't really have arbitrary contents, can you? + +<div class="alert alert-success"> +**Best Practice:** only use `transclude: true` when you want to create a directive that wraps arbitrary content. +</div> + +Next, we want to add buttons to this dialog box, and allow someone using the directive to bind their own +behavior to it. + +<example module="docsIsoFnBindExample"> +  <file name="script.js"> +    angular.module('docsIsoFnBindExample', []) +      .controller('Ctrl', function($scope, $timeout) { +        $scope.name = 'Tobias'; +        $scope.hideDialog = function () { +          $scope.dialogIsHidden = true; +          $timeout(function () { +            $scope.dialogIsHidden = false; +          }, 2000); +        }; +      }) +      .directive('myDialog', function() { +        return { +          restrict: 'E', +          transclude: true, +          scope: { +            'close': '&onClose' +          }, +          templateUrl: 'my-dialog-close.html' +        }; +      }); +  </file> +  <file name="index.html"> +    <div ng-controller="Ctrl"> +      <my-dialog ng-hide="dialogIsHidden" on-close="dialogIsHidden = true"> +        Check out the contents, {{name}}! +      </my-dialog> +    </div> +  </file> +  <file name="my-dialog-close.html"> +    <div class="alert"> +      <a href class="close" ng-click="close()">×</a> +      <div ng-transclude></div> +    </div> +  </file> +</example> + +We want to run the function we pass by invoking it from the directive's scope, but have it run +in the context of the scope where its registered. + +We saw earlier how to use `=prop` in the `scope` option, but in the above example, we're using +`&prop` instead. `&` bindings expose a function to an isolated scope allowing the isolated scope +to invoke it, but maintaining the original scope of the function. So when a user clicks the +`x` in the dialog, it runs `Ctrl`'s `close` function. + +<div class="alert alert-success"> +**Best Practice:** use `&prop` in the `scope` option when you want your directive +to expose an API for binding to behaviors. +</div> + + +### Creating a Directive that Adds Event Listeners + +Previously, we used the `link` function to create a directive that manipulated its +DOM elements. Building upon that example, let's make a directive that reacts to events on +its elements. + +For instance, what if we wanted to create a directive that lets a user drag an +element? + +<example module="dragModule"> +  <file name="script.js"> +    angular.module('dragModule', []). +      directive('myDraggable', function($document) { +        return function(scope, element, attr) { +          var startX = 0, startY = 0, x = 0, y = 0; + +          element.css({ +           position: 'relative', +           border: '1px solid red', +           backgroundColor: 'lightgrey', +           cursor: 'pointer' +          }); + +          element.on('mousedown', function(event) { +            // Prevent default dragging of selected content +            event.preventDefault(); +            startX = event.screenX - x; +            startY = event.screenY - y; +            $document.on('mousemove', mousemove); +            $document.on('mouseup', mouseup); +          }); + +          function mousemove(event) { +            y = event.screenY - startY; +            x = event.screenX - startX; +            element.css({ +              top: y + 'px', +              left:  x + 'px' +            }); +          } + +          function mouseup() { +            $document.unbind('mousemove', mousemove); +            $document.unbind('mouseup', mouseup); +          } +        } +      }); +  </file> +  <file name="index.html"> +    <span my-draggable>Drag ME</span> +  </file> +</example> -This will not render properly, unless we do some scope magic. -The first issue we have to solve is that the dialog box template expects `title` to be defined, but -the place of instantiation would like to bind to `username`. Furthermore the buttons expect the -`onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the -widget. To solve the mapping issue we use the `locals` to create local variables which the template -expects as follows: -<pre> -  scope: { -    title: '@',             // the title uses the data-binding from the parent scope -    onOk: '&',              // create a delegate onOk function -    onCancel: '&',          // create a delegate onCancel function -    visible: '='            // set up visible to accept data-binding -  } -</pre> +### Creating Directives that Communicate -Creating local properties on widget scope creates two problems: +You can compose any directives by using them within templates. -  1. isolation - if the user forgets to set `title` attribute of the dialog widget the dialog -     template will bind to parent scope property. This is unpredictable and undesirable. +Sometimes, you want a component that's built from a combination of directives. -  2. transclusion - the transcluded DOM can see the widget locals, which may overwrite the -     properties which the transclusion needs for data-binding. In our example the `title` -     property of the widget clobbers the `title` property of the transclusion. +Imagine you want to have a container with tabs in which the contents of the container correspond +to which tab is active. +<example module="docsTabsExample"> +  <file name="script.js"> +    angular.module('docsTabsExample', []) +      .directive('myTabs', function() { +        return { +          restrict: 'E', +          transclude: true, +          scope: {}, +          controller: function($scope) { +            var panes = $scope.panes = []; + +            $scope.select = function(pane) { +              angular.forEach(panes, function(pane) { +                pane.selected = false; +              }); +              pane.selected = true; +            }; + +            this.addPane = function(pane) { +              if (panes.length == 0) { +                $scope.select(pane); +              } +              panes.push(pane); +            }; +          }, +          templateUrl: 'my-tabs.html' +        }; +      }) +      .directive('myPane', function() { +        return { +          require: '^myTabs', +          restrict: 'E', +          transclude: true, +          scope: { +            title: '@' +          }, +          link: function(scope, element, attrs, tabsCtrl) { +            tabsCtrl.addPane(scope); +          }, +          templateUrl: 'my-pane.html' +        }; +      }); +  </file> +  <file name="index.html"> +    <my-tabs> +      <my-pane title="Hello"> +        <h4>Hello</h4> +        <p>Lorem ipsum dolor sit amet</p> +      </my-pane> +      <my-pane title="World"> +        <h4>World</h4> +        <em>Mauris elementum elementum enim at suscipit.</em> +        <p><a href ng-click="i = i + 1">counter: {{i || 0}}</a></p> +      </my-pane> +    </my-tabs> +  </file> +  <file name="my-tabs.html"> +    <div class="tabbable"> +      <ul class="nav nav-tabs"> +        <li ng-repeat="pane in panes" ng-class="{active:pane.selected}"> +          <a href="" ng-click="select(pane)">{{pane.title}}</a> +        </li> +      </ul> +      <div class="tab-content" ng-transclude></div> +    </div> +  </file> +  <file name="my-pane.html"> +    <div class="tab-pane" ng-show="selected" ng-transclude> +    </div> +  </file> +</example> -To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An -isolated scope does not prototypically inherit from the child scope, and therefore we don't have -to worry about accidentally clobbering any properties. +The `myPane` directive has a `require` option with value `^myTabs`. When a directive uses this +option, `$compile` will throw an error unless the specified controller is found. The `^` prefix +means that this directive searches for the controller on its parents (without the `^` prefix, the +directive would look for the controller on just its own element). -However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget -isolated scope then it will not be able to bind to anything. For this reason the transcluded scope -is a child of the original scope, before the widget created an isolated scope for its local -variables. This makes the transcluded and widget isolated scope siblings. +So where does this `myTabs` controller come from? Directives can specify controllers using +the unsurprisingly named `controller` option. As you can see, the `myTabs` directive uses this +option. Just like `ngController`, this option attaches a controller to the template of the directive. -This may seem to be unexpected complexity, but it gives the widget user and developer the least -surprise. +Looking back at `myPane`'s definition, notice the last argument in its `link` function: `tabsCtrl`. +When a directive requires a controller, it receives that controller as the fourth argument of its +`link` function. Taking advantage of this, `myPane` can call the `addPane` function of `myTabs`. -Therefore the final directive definition looks something like this: +Savvy readers may be wondering what the difference is between `link` and `controller`. +The basic difference is that `controller` can expose an API, and `link` functions can interact with +controllers using `require`. + +<div class="alert alert-success"> +**Best Practice:** use `controller` when you want to expose an API to other directives. +Otherwise use `link`. +</div> + +### Summary + +Here we've seen the main use cases for directives. Each of these samples acts as a good starting +point for creating your own directives. + +You might also be interested in an in-depth explanation of the compilation process that's +available in the {@link guide/compiler compiler guide}. + +The {@link api/ng.$compile `$compile` API} page has a comprehensive list of directive options for reference. -<pre> -transclude: true, -scope: { -    title: '@',             // the title uses the data-binding from the parent scope -    onOk: '&',              // create a delegate onOk function -    onCancel: '&',          // create a delegate onCancel function -    visible: '='            // set up visible to accept data-binding -}, -restrict: 'E', -replace: true -</pre> - -<a name="Components"></a> -# Creating Components - -It is often desirable to replace a single directive with a more complex DOM structure. This -allows the directives to become a short hand for reusable components from which applications -can be built. - -Following is an example of building a reusable widget. - -<doc:example module="zippyModule"> -  <doc:source> -   <script> -     function Ctrl3($scope) { -       $scope.title = 'Lorem Ipsum'; -       $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; -     } - -     angular.module('zippyModule', []) -       .directive('zippy', function(){ -         return { -           restrict: 'C', -           // This HTML will replace the zippy directive. -           replace: true, -           transclude: true, -           scope: { title:'@zippyTitle' }, -           template: '<div>' + -                       '<div class="title">{{title}}</div>' + -                       '<div class="body" ng-transclude></div>' + -                     '</div>', -           // The linking function will add behavior to the template -           link: function(scope, element, attrs) { -             // Title element -             var title = angular.element(element.children()[0]), -             // Opened / closed state -             opened = true; - -             // Clicking on title should open/close the zippy -             title.on('click', toggle); - -             // Toggle the closed/opened state -             function toggle() { -               opened = !opened; -               element.removeClass(opened ? 'closed' : 'opened'); -               element.addClass(opened ? 'opened' : 'closed'); -             } - -             // initialize the zippy -             toggle(); -           } -         } -       }); -   </script> -   <style> -     .zippy { -       border: 1px solid black; -       display: inline-block; -       width: 250px; -     } -     .zippy.opened > .title:before { content: '▼ '; } -     .zippy.opened > .body { display: block; } -     .zippy.closed > .title:before { content: '► '; } -     .zippy.closed > .body { display: none; } -     .zippy > .title { -       background-color: black; -       color: white; -       padding: .1em .3em; -       cursor: pointer; -     } -     .zippy > .body { -       padding: .1em .3em; -     } -   </style> -   <div ng-controller="Ctrl3"> -     Title: <input ng-model="title"> <br> -     Text: <textarea ng-model="text"></textarea> -     <hr> -     <div class="zippy" zippy-title="Details: {{title}}...">{{text}}</div> -   </div> -  </doc:source> -  <doc:scenario> -    it('should bind and open / close', function() { -      input('title').enter('TITLE'); -      input('text').enter('TEXT'); -      expect(element('.title').text()).toEqual('Details: TITLE...'); -      expect(binding('text')).toEqual('TEXT'); - -      expect(element('.zippy').prop('className')).toMatch(/closed/); -      element('.zippy > .title').click(); -      expect(element('.zippy').prop('className')).toMatch(/opened/); -    }); -  </doc:scenario> -</doc:example> | 
