diff options
| author | Misko Hevery | 2011-12-14 02:55:31 +0100 | 
|---|---|---|
| committer | Misko Hevery | 2012-01-25 11:53:59 -0800 | 
| commit | 4804c83b7db5770d5d02eea9eea4cc012b4aa524 (patch) | |
| tree | be5ae1743179704cc1638f186cddba8d6e3fa37d /docs/content/api | |
| parent | e2b1d9e994e50bcd01d237302a3357bc7ebb6fa5 (diff) | |
| download | angular.js-4804c83b7db5770d5d02eea9eea4cc012b4aa524.tar.bz2 | |
docs(compiler): update the compiler docs
Diffstat (limited to 'docs/content/api')
| -rw-r--r-- | docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc | 505 | ||||
| -rw-r--r-- | docs/content/api/index.ngdoc | 4 | 
2 files changed, 506 insertions, 3 deletions
| diff --git a/docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc b/docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc new file mode 100644 index 00000000..5a258179 --- /dev/null +++ b/docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc @@ -0,0 +1,505 @@ +@ngdoc overview +@name angular.module.ng.$compileProvider.directive +@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 in element names, attributes, class names, as well as comments. Here +are some equivalent examples of invoking `ngBind`. + +<pre> +  <span ng-bind="exp"></span> +  <span class="ng-bind: exp;"></span> +  <ng-bind></ng-bind> +  <!-- directive: ng-bind exp --!> +</pre> + +Directives can be invoked in many different ways, but are equivalent in the end result as shown in +the following example. + +<doc:example> +  <doc:source > +   <script> +     function Ctrl1($scope) { +       $scope.name = 'angular'; +     } +   </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/> +     <span class="ng-bind: name;"> <span class="ng-bind: name;"></span> <br/> +   </div> +  </doc:source> +  <doc:scenario> +    it('should load template1.html', function() { +      expect(element('div[ng-controller="Ctrl1"] span[ng-bind]').text()).toBe('angular'); +    }); +  </doc:scenario> +</doc:example> + +# String interpolation + +During the compilation process the {@link angular.module.ng.$compile compiler} matches text and +attributes using the {@link angular.module.ng.$interpolate $interpolate} service to see if they +contain embedded expressions. These expressions are registered as {@link +angular.module.ng.$rootScope.Scope#.watch watches} and will update as part of normal {@link +angular.module.ng.$rootScope.Scope#.digest digest} cycle. An example of interpolation is shown +here: + +<pre> +<img src="img/{{username}}.jpg">Hello {{username}}!</img> +</pre> + +# Compilation process, and directive matching + +Compilation of HTML happens in three phases: + +  1. First 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 then on DOM elements. + +  2. The compilation of the DOM is performed by the call to {@link angular.module.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 compile function has a chance to modify the DOM structure +  and is responsible for producing a `link()` function explained next. The {@link +  angular.module.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. + +  3. Link the template with scope by calling the liking 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 +  angular.module.ng.$rootScope.Scope#.watch watches} with the {@link +  angular.module.ng.$rootScope.Scope scope}. The result of this is a live binding between the +  scope and the DOM. A change in the scope is reflected in the DOM. + +<pre> +  var $compile = ...; // injected into your code +  var scope = ...; + +  var html = '<div ng-bind='exp'></div>'; + +  // Step 1: parse HTML into DOM element +  var template = angular.element(html); + +  // Step 2: compile the template +  var linkFn = $compile(template); + +  // Step 3: link the compiled template with the scope. +  linkFn(scope); +</pre> + +## Reasons behind the compile/link separation + +At this point you may wonder why is the compile process broken down to a compile and link phase. +To understand this, lets look at a real world example with repeater: + +<pre> +  Hello {{user}}, you have these actions: +  <ul> +    <li ng:repeat="action in user.actions"> +      {{action.description}} +    </li> +  </ul> +</pre> + +The short answer is that compile and link separation is needed any time a change in model causes +a change in DOM structure such as in repeaters. + +When the above example is compiled, the compiler visits every node and looks for directives. The +`{{user}}` is an example of {@link angular.module.ng.$interpolate interpolation} directive. {@link +angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} is another directive. But {@link +angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} 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 and 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.descriptions}}` evaluate against the right {@link angular.module.ng.$rootScope.Scope +scope}. A naive method would be to simply insert a copy of the `li` elemnt and then compile it. +But compiling on every `li` element clone would be slow, since the compilation requires that we +traverse the DOM tree and look for directives and execute them. If we put the compilation inside a +repeater which needs to unroll 100 items we would quickly run into performance problem. + +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 angular.module.ng.$rootScope.Scope scope} and the specific +instance of an `li` is performed. + +{@link angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} works by preventing the +compilation process form descending into `li` element. Instead the {@link +angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} directive compiles `li` +seperatly. The result of 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 `li` +element. At runtime the {@link angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} +watches the expression and as items are added to the array it clones the `li` element, creates a +new {@link angular.module.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 then +    transforming the template DOM element. Any operation which can be shared among the instance of +    directives should be moved to the compile function for performance reasons. + +  * *link function* - It is rare for the directive not to have a link function. Link function +    allows the directive to register listeners to the specific cloned DOM element instance as well +    as to copy content into the DOM from the scope. + + +# Writing directives (short version) + +In this example we will build a directive which displays the current time. + +<doc:example module="time"> +  <doc:source> +   <script> +     function Ctrl2($scope) { +       $scope.format = 'M/d/yy h:mm:ss a'; +     } + +     angular.module('time', [], function($compileProvider) { +       // Register the 'myCurrentTime' directive factory method. +       // We inject $defer and dateFilter service since the factory method is DI. +       $compileProvider.directive('myCurrentTime', function($defer, dateFilter) { +         // return the directive link function. (compile function not needed) +         return function(scope, element, attrs) { +           var format,  // date format +               deferId; // deferId, 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 deferId for canceling +             deferId = $defer(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 ofter the DOM element was removed. +           element.bind('$destroy', function() { +             $defer.cancel(deferId); +           }); + +           updateLater(); // kick of 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:scenario> +  </doc:scenario> +</doc:example> + + +# Writing directives (long version) + +The full skeleton of the directive is shown here: + +<pre> +  var $compileProvider = ...; + +  $compileProvider.directive('directiveName', function factory(injectables) { +    var directiveDefinitionObject = { +      priority: 0, +      template: '<div></div>', +      templateUrl: 'directive.html', +      restrict: 'EACM', +      scope: false, +      compile: function compile(tElement, tAttrs) { +        return { +          pre: function preLink(scope, iElement, iAttrs) { ... }, +          post: function postLink(scope, iElement, iAttrs) { ... } +        } +      }, +      link: function postLink(scope, iElement, iAttrs) { ... } +    }; +    return directiveDefinitionObject; +  }); +</pre> + +In most cases you will not need such fine control and so the above can be simplified. All of the +different parts of this skeleton are explained in following sections. In this section we are +interested only isomers of this skeleton. + +It is rare that you need `preLink` method since most directives use the `postLink` method. +Therefore the above can be simplified as: + +<pre> +  var $compileProvider = ...; + +  $compileProvider.directive('directiveName', function factory(injectables) { +    var directiveDefinitionObject = { +      compile: function compile(tElement, tAttrs) { +        return function postLink(scope, iElement, iAttrs) { ... } +      } +    }; +    return directiveDefinitionObject; +  }); +</pre> + +Most directives concern themselves only with instances not with template transformations allowing +further simplification: + +<pre> +  var $compileProvider = ...; + +  $compileProvider.directive('directiveName', function factory(injectables) { +    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 +{@link angular.module.ng.$compile compiler} matches the directive for the first time. You can +perform any initialization work here. The method is invoked using the {@link +http://localhost:8000/build/docs/api/angular.module.AUTO.$injector#invoke $injector.invoke} which +makes it injectable following all of the rules of injection annotation. + +## Directive Definition Object + +The directive definition object provides instructions to the {@link angular.module.ng.$compile +compiler}. The attributes are: + +  * `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. Higher `priority` goes +    first. The order of directives within the same priority is undefined. + +  * `terminal` - If set to true then the current `priority` will be the last set of directives +    which will execute (this means that any directives at the current priority will still execute +    as the order of execution on same `priority` is undefined). + +  * `scope` - If set to true, then a new scope will be created for this directive. It is an error +    to have two directives on the same element both requesting new scope. The new scope rule does +    not apply for the root of the template since the root of the template always gets a new scope. + +  * `restrict` - String of subset of `EACM` which restricts the directive to a specific directive +    declaration style. + +    * `E` - Element name: `<my-directive></my-directive>` +    * `A` - Attribute: `<div my-directive="exp"></div>` +    * `C` - Class: `<div class="my-directive: exp;"></div>` +    * `M` - Comment: `<!-- directive: my-directive exp -->` + +  * `template` - replace the current element with the contents of the HTML. The HTML may have +    `<<content>>` string embedded in itself, in which case the current element content +    will be transferred there. The replacement process migrates all of the attributes / classes +    from the old element to the new one. See Creating Widgets section below for more information. + +  * `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. + +  * `compile`: This is the compile function described in the section below. + +  * `link`: This is the link function described in the section below. This property is used only +    if the `compile` property is not defined. + +## Compile function + +<pre> +  function compile(tElement, tAttrs) { ... } +</pre> + +Compile function deals with transforming the template DOM. Since most directives do not do +template transformation, it is not used often. Examples which require compile functions are +directives which transform template DOM such as {@link +angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} or load the contents +asynchronously such as {@link angular.module.ng.$compileProvider.directive.ng:view ng:view}. The +compile functions takes the following arguments. + +  * `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. + +  * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared +    between all directive compile functions. See {@link +    angular.module.ng.$compileProvider.directive.Attributes Attributes} + +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 other the DOM transformation. +Specifically listener registration as not allowed inside the compile function. + +## Link function + +<pre> +  function link(scope, iElement, iAttrs) { ... } +</pre> + +Compile 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. + +  * `scope` - {@link angular.module.ng.$rootScope.Scope Scope} - The scope to be used be the +    directive for registering {@link angular.module.ng.$rootScope.Scope#.watch watches}. + +  * `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. + +  * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared +    between all directive linking functions. See {@link +    angular.module.ng.$compileProvider.directive.Attributes Attributes} + + +### Pre link function + +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. + +### Post link function + +Executed after the child elements are linked. Safe to do DOM transformation in here. + +## Attributes + +Attributes object is a way of accessing element attributes which: + +  * *normalize attribute names:* Since a directive such as 'ngBind' can be expressed in many ways +    sucha s as 'ng:bind', or 'x-ng-bind', the attributes object allows for a normalize accessed 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. + +  * *supports interpolation:* Interpolation attributes are assigned to the attribute object +    allowing other directives to read the interpolated value. + + +# Creating Widgets + +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', [], function($compileProvider) { +       $compileProvider.directive('zippy', function(){ +         return { +           // This HTML will replace the zippy directive. +           replace: true, +           template: '<div>' + +                       '<div class="title"></div>' + +                       '<div class="body"><<content>></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; + +             // Watch the zippy-title attribute, copy changes to title element +             scope.$watch( +               function(){ return attrs.zippyTitle; }, +               function(value) { return title.text(value); } +             ); + +             // Clicking on title should open/close the zippy +             title.bind('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> + + diff --git a/docs/content/api/index.ngdoc b/docs/content/api/index.ngdoc index ccacef80..9f1bb398 100644 --- a/docs/content/api/index.ngdoc +++ b/docs/content/api/index.ngdoc @@ -4,9 +4,7 @@  ## Angular Compiler API -* {@link angular.widget Widgets} - Angular custom DOM element -* {@link angular.directive Directives} - Angular DOM element attributes -* {@link angular.markup Markup} and {@link angular.attrMarkup Attribute Markup} +* {@link angular.module.ng.$compileProvider.directive Directives} - Angular DOM element attributes  * {@link angular.module.ng.$filter Filters} - Angular output filters  * {@link angular.module.ng.$compile $compile} - Template compiler | 
