aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/guide/directive.ngdoc
diff options
context:
space:
mode:
Diffstat (limited to 'docs/content/guide/directive.ngdoc')
-rw-r--r--docs/content/guide/directive.ngdoc1436
1 files changed, 792 insertions, 644 deletions
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/>
- &lt;span ng-bind="name"&gt; <span ng-bind="name"></span> <br/>
- &lt;span ng:bind="name"&gt; <span ng:bind="name"></span> <br/>
- &lt;span ng_bind="name"&gt; <span ng_bind="name"></span> <br/>
- &lt;span data-ng-bind="name"&gt; <span data-ng-bind="name"></span> <br/>
- &lt;span x-ng-bind="name"&gt; <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()">&times;</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>