Improve this Doc  View Source

$compile

  1. - $compileProvider
  2. - service in module ng

Compiles an HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.

The compilation is a process of walking the DOM tree and matching DOM elements to directives.

Note: This document is an in-depth reference of all directive options. For a gentle introduction to directives with examples of common use cases, see the directive guide.

Comprehensive Directive API

There are many different options for 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 (all other properties will have the default values).

Best Practice: It's recommended to use the "directive definition object" form.

Here's an example directive declared with a Directive Definition Object:

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) { ... },
    transclude: false,
    restrict: 'A',
    templateNamespace: 'html',
    scope: false,
    controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
    controllerAs: 'stringIdentifier',
    bindToController: false,
    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;
});
Note: Any unspecified options will use the default value. You can see the default values below.

Therefore the above can be simplified as:

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) { ... }
});

Directive Definition Object

The directive definition object provides instructions to the compiler. The attributes are:

multiElement

When this property is set to true, the HTML compiler will collect DOM nodes between nodes with the attributes directive-name-start and directive-name-end, and group them together as the directive elements. It is recommended that this feature be used on directives which are not strictly behavioral (such as ngClick), and which do not manipulate or replace child nodes (such as ngInclude).

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. Pre-link functions are also run in priority order, but post-link functions are run in reverse order. The order of directives with the same priority is undefined. The default priority is 0.

terminal

If set to true then the current priority will be the last set of directives which will execute (any directives at the current priority will still execute as the order of execution on same priority is undefined). Note that expressions and other directives used in the directive's template will also be excluded from execution.

scope

The scope property can be true, an object or a falsy value:

The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the directive's element. These local properties are useful for aliasing values for templates. The keys in the object hash map to the name of the property on the isolate scope; the values define how the property is bound to the parent scope, via matching attributes on the directive's element:

In general it's possible to apply more than one directive to one element, but there might be limitations depending on the type of scope required by the directives. The following points will help explain these limitations. For simplicity only two directives are taken into account, but it is also applicable for several directives:

bindToController

This property is used to bind scope properties directly to the controller. It can be either true or an object hash with the same format as the scope property. Additionally, a controller alias must be set, either by using controllerAs: 'myAlias' or by specifying the alias in the controller definition: controller: 'myCtrl as myAlias'.

When an isolate scope is used for a directive (see above), bindToController: true will allow a component to have its properties bound to the controller, rather than to scope.

After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller properties. You can access these bindings once they have been initialized by providing a controller method called $onInit, which is called after all the controllers on an element have been constructed and had their bindings initialized.

Deprecation warning: although bindings for non-ES6 class controllers are currently bound to this before the controller constructor is called, this use is now deprecated. Please place initialization code that relies upon bindings inside a $onInit method on the controller, instead.

It is also possible to set bindToController to an object hash with the same format as the scope property. This will set up the scope bindings to the controller directly. Note that scope can still be used to define which kind of scope is created. By default, no scope is created. Use scope: {} to create an isolate scope (useful for component directives).

If both bindToController and scope are defined and have object hashes, bindToController overrides scope.

controller

Controller constructor function. The controller is instantiated before the pre-linking phase and can be accessed by other directives (see 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:

The controller can provide the following methods that act as life-cycle hooks:

require

Require another directive and inject its controller as the fourth argument to the linking function. The require property can be a string, an array or an object:

If the require property is an object and bindToController is truthy, then the required controllers are bound to the controller using the keys of the require property. This binding occurs after all the controllers have been constructed but before $onInit is called. See the $compileProvider helper for an example of how this can be used.

If no such required directive(s) can be found, or if the directive does not have a controller, then an error is raised (unless no link function is specified and the required controllers are not being bound to the directive controller, in which case error checking is skipped). The name can be prefixed with:

controllerAs

Identifier name for a reference to the controller in the directive's scope. This allows the controller to be referenced from the directive template. This is especially useful when a directive is used as component, i.e. with an isolate scope. It's also possible to use it in a directive without an isolate / new scope, but you need to be aware that the controllerAs reference might overwrite a property that already exists on the parent scope.

restrict

String of subset of EACM which restricts the directive to a specific directive declaration style. If omitted, the defaults (elements and attributes) are used.

templateNamespace

String representing the document type used by the markup in the template. AngularJS needs this information as those elements need to be created and cloned in a special way when they are defined outside their usual containers like <svg> and <math>.

If no templateNamespace is specified, then the namespace is considered to be html.

template

HTML markup that may:

Value may be:

templateUrl

This is similar to template but the template is loaded from the specified URL, asynchronously.

Because template loading is asynchronous the compiler will suspend compilation of directives on that element for later when the template has been resolved. In the meantime it will continue to compile and link sibling and parent elements as though this element had not contained any directives.

The compiler does not suspend the entire compilation to wait for templates to be loaded because this would result in the whole app "stalling" until all templates are loaded asynchronously - even in the case when only one deeply nested directive has templateUrl.

Template loading is asynchronous even if the template has been preloaded into the $templateCache

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 $sce.getTrustedResourceUrl.

replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)

specify what the template should replace. Defaults to false.

The replacement process migrates all of the attributes / classes from the old element to the new one. See the Directives Guide for an example.

There are very few scenarios where element replacement is required for the application function, the main one being reusable custom components that are used within SVG contexts (because SVG doesn't work with custom elements in the DOM tree).

transclude

Extract the contents of the element where the directive appears and make it available to the directive. The contents are compiled and provided to the directive as a transclusion function. See the Transclusion section below.

compile

function compile(tElement, tAttrs, transclude) { ... }

The compile function deals with transforming the template DOM. Since most directives do not do template transformation, it is not used often. The compile function takes the following arguments:

Note: The template instance and the link instance may be different objects if the template has been cloned. For this reason it is not safe to do anything other than DOM transformations that apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration should be done in a linking function rather than in a compile function.
Note: The compile function cannot handle directives that recursively use themselves in their own templates or compile functions. Compiling these directives results in an infinite loop and stack overflow errors. This can be avoided by manually using $compile in the postLink function to imperatively compile a directive's template instead of relying on automatic template compilation via template or templateUrl declaration or manual compilation inside the compile function.
Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.

A compile function can have a return value which can be either a function or an object.

This property is used only if the compile property is not defined.

function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }

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.

Pre-linking 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-linking function

Executed after the child elements are linked.

Note that child elements that contain templateUrl directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs.

It is safe to do DOM transformation in the post-linking function on elements that are not waiting for their async templates to be resolved.

Transclusion

Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and copying them to another part of the DOM, while maintaining their connection to the original AngularJS scope from where they were taken.

Transclusion is used (often with ngTransclude) to insert the original contents of a directive's element into a specified place in the template of the directive. The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded content has access to the properties on the scope from which it was taken, even if the directive has isolated scope. See the Directives Guide.

This makes it possible for the widget to have private state for its template, while the transcluded content has access to its originating scope.

Note: When testing an element transclude directive you must not place the directive at the root of the DOM fragment that is being compiled. See Testing Transclusion Directives.

There are three kinds of transclusion depending upon whether you want to transclude just the contents of the directive's element, the entire element or multiple parts of the element contents:

Mult-slot transclusion is declared by providing an object for the transclude property.

This object is a map where the keys are the name of the slot to fill and the value is an element selector used to match the HTML to the slot. The element selector should be in normalized form (e.g. myElement) and will match the standard element variants (e.g. my-element, my:element, data-my-element, etc).

For further information check out the guide on Matching Directives

If the element selector is prefixed with a ? then that slot is optional.

For example, the transclude object { slotA: '?myCustomElement' } maps <my-custom-element> elements to the slotA slot, which can be accessed via the $transclude function or via the ngTransclude directive.

Slots that are not marked as optional (?) will trigger a compile time error if there are no matching elements in the transclude content. If you wish to know if an optional slot was filled with content, then you can call $transclude.isSlotFilled(slotName) on the transclude function passed to the directive's link function and injectable into the directive's controller.

Transclusion Functions

When a directive requests transclusion, the compiler extracts its contents and provides a transclusion function to the directive's link function and controller. This transclusion function is a special linking function that will return the compiled contents linked to a new transclusion scope.

If you are just using ngTransclude then you don't need to worry about this function, since ngTransclude will deal with it for us.

If you want to manually control the insertion and removal of the transcluded content in your directive then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery object that contains the compiled DOM, which is linked to the correct transclusion scope.

When you call a transclusion function you can pass in a clone attach function. This function accepts two parameters, function(clone, scope) { ... }, where the clone is a fresh compiled copy of your transcluded content and the scope is the newly created transclusion scope, to which the clone is bound.

Best Practice: Always provide a cloneFn (clone attach function) when you call a transclude function since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.

It is normal practice to attach your transcluded content (clone) to the DOM inside your clone attach function:

var transcludedContent, transclusionScope;

$transclude(function(clone, scope) {
  element.append(clone);
  transcludedContent = clone;
  transclusionScope = scope;
});

Later, if you want to remove the transcluded content from your DOM then you should also destroy the associated transclusion scope:

transcludedContent.remove();
transclusionScope.$destroy();
Best Practice: if you intend to add and remove transcluded content manually in your directive (by calling the transclude function to get the DOM and calling element.remove() to remove it), then you are also responsible for calling $destroy on the transclusion scope.

The built-in DOM manipulation directives, such as ngIf, ngSwitch and ngRepeat automatically destroy their transcluded clones as necessary so you do not need to worry about this if you are simply using ngTransclude to inject the transclusion into your directive.

Transclusion Scopes

When you call a transclude function it returns a DOM fragment that is pre-bound to a transclusion scope. This scope is special, in that it is a child of the directive's scope (and so gets destroyed when the directive's scope gets destroyed) but it inherits the properties of the scope from which it was taken.

For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look like this:

<div ng-app>
  <div isolate>
    <div transclusion>
    </div>
  </div>
</div>

The $parent scope hierarchy will look like this:

- $rootScope
  - isolate
    - transclusion

but the scopes will inherit prototypically from different scopes to their $parent.

- $rootScope
  - transclusion
- isolate

Attributes

The Attributes object - passed as a parameter in the link() or compile() functions. It has a variety of uses.

function linkingFn(scope, elm, attrs, ctrl) {
  // get the attribute value
  console.log(attrs.ngModel);

  // change the attribute
  attrs.$set('ngModel', 'new value');

  // observe changes to interpolated attribute
  attrs.$observe('ngModel', function(value) {
    console.log('ngModel has changed value to ' + value);
  });
}

Example

Note: Typically directives are registered with module.directive. The example below is to illustrate how $compile works.

  Edit in Plunker
<script>
  angular.module('compileExample', [], function($compileProvider) {
    // configure new 'compile' directive by passing a directive
    // factory function. The factory function injects the '$compile'
    $compileProvider.directive('compile', function($compile) {
      // directive factory creates a link function
      return function(scope, element, attrs) {
        scope.$watch(
          function(scope) {
             // watch the 'compile' expression for changes
            return scope.$eval(attrs.compile);
          },
          function(value) {
            // when the 'compile' expression changes
            // assign it into the current DOM
            element.html(value);

            // compile the new DOM and link it to the current
            // scope.
            // NOTE: we only compile .childNodes so that
            // we don't get into infinite loop compiling ourselves
            $compile(element.contents())(scope);
          }
        );
      };
    });
  })
  .controller('GreeterController', ['$scope', function($scope) {
    $scope.name = 'Angular';
    $scope.html = 'Hello {{name}}';
  }]);
</script>
<div ng-controller="GreeterController">
  <input ng-model="name"> <br/>
  <textarea ng-model="html"></textarea> <br/>
  <div compile="html"></div>
</div>
it('should auto compile', function() {
  var textarea = $('textarea');
  var output = $('div[compile]');
  // The initial state reads 'Hello Angular'.
  expect(output.getText()).toBe('Hello Angular');
  textarea.clear();
  textarea.sendKeys('{{name}}!');
  expect(output.getText()).toBe('Angular!');
});

Usage

$compile(element, transclude, maxPriority);

Arguments

Param Type Details
element stringDOMElement

Element or HTML string to compile into a template function.

transclude function(angular.Scope, cloneAttachFn=)

function available to directives - DEPRECATED.

Note: Passing a transclude function to the $compile function is deprecated, as it e.g. will not use the right outer scope. Please pass the transclude function as a parentBoundTranscludeFn to the link function instead.
maxPriority number

only apply directives lower than given priority (Only effects the root element(s), not their children)

Returns

function(scope, cloneAttachFn=, options=)

a link function which is used to bind template (a DOM element/tree) to a scope. Where:

  • scope - A Scope to bind to.
  • cloneAttachFn - If cloneAttachFn is provided, then the link function will clone the template and call the cloneAttachFn function allowing the caller to attach the cloned elements to the DOM document at the appropriate place. The cloneAttachFn is called as:
    cloneAttachFn(clonedElement, scope) where:

    • clonedElement - is a clone of the original element passed into the compiler.
    • scope - is the current scope with which the linking function is working with.
  • options - An optional object hash with linking options. If options is provided, then the following keys may be used to control linking behavior:

    • parentBoundTranscludeFn - the transclude function made available to directives; if given, it will be passed through to the link functions of directives found in element during compilation.
    • transcludeControllers - an object hash with keys that map controller names to a hash with the key instance, which maps to the controller instance; if given, it will make the controllers available to directives on the compileNode:
      {
        parent: {
          instance: parentControllerInstance
        }
      }
      
    • futureParentElement - defines the parent to which the cloneAttachFn will add the cloned elements; only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements). See also the directive.controller property.

Calling the linking function returns the element of the template. It is either the original element passed in, or the clone of the element if the cloneAttachFn is provided.

After linking the view is not updated until after a call to $digest which typically is done by Angular automatically.

If you need access to the bound view, there are two ways to do it:

  • If you are not asking the linking function to clone the template, create the DOM element(s) before you send them to the compiler and keep this reference around.

    var element = $compile('<p>{{total}}</p>')(scope);
    
  • if on the other hand, you need the element to be cloned, the view reference from the original example would not point to the clone, but rather to the original template that was cloned. In this case, you can access the clone via the cloneAttachFn:

    var templateElement = angular.element('<p>{{total}}</p>'),
        scope = ....;
    
    var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
      //attach the clone to DOM document at the right place
    });
    
    //now we have reference to the cloned DOM via `clonedElement`
    

For information on how the compiler works, see the Angular HTML Compiler section of the Developer Guide.