diff options
Diffstat (limited to 'docs/content/guide/directive.ngdoc')
| -rw-r--r-- | docs/content/guide/directive.ngdoc | 1436 |
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/> - <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> |
