Directive is a way to teach HTML to play some new tricks. During DOM compilation, directives match HTML and execute. This allows directive registration behavior or converting DOM structures.
Angular comes with a set of built-in directives, which is very helpful for building a Web App. If you continue to expand, you can define domain specific language (DSL) in HTML.
1. Quote directives in HTML
Directive has camel cased style naming, such as ngBind (it seems to be unusable in properties ~). However, directive can also be named with a snake bottom type (snake case), which needs to be connected through: (colon), - (minus) or _ (underscore). As an optional option, directive can be prefixed with "x-" or "data-" to meet HTML verification needs. Here are the legal names of directives:
Directive can be placed in element names, attributes, classes, and comments. The following is the equivalent way to quote myDir, the directive. (But many directives are limited to the use of "properties")
<span my-dir="exp"></span><span></span><my-dir></my-dir><!-- directive: my-dir exp -->
Directive can be cited in a variety of ways, and the following lists N equivalent ways:
<!DOCTYPE HTML><html lang="zh-cn" ng-app><head> <meta charset="UTF-8"> <title>invoke-directive</title> <style type="text/css"> .ng-cloak { display: none; } </style></head><body><div ng-controller="MyCtrl"> Hello <input ng-model="name"/><hr/> ngBind="name" This cannot be used~~ <span ngBind="name"></span><br/> ng:bind="name"<span ng:bind="name"></span><br/> ng_bind="name"<span ng_bind="name"></span><br/> ng-bind="name"<span ng-bind="name"></span><br/> data-ng-bind="name"<span data-ng-bind="name"></span><br/> x-ng-bind="name"<span x-ng-bind="name"></span><br/></div><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> function MyCtrl($scope) { $scope.name = "beauty~~"; }</script></body></html>2. String interpolation
During the compilation process, compiler matches text with embedded expressions in attributes (such as {{something}}) through the $interpolate service. These expressions will be registered as watches and will be updated together as part of the digest cycle (wasn't it a digest-loop before?!). Here is a simple interpolation:
<img src="img/{{username}}.jpg"/> Hello {{username}}!
3. Compilation process, and directive matching
Three steps to "compile" HTML:
1. First, convert HTML into DOM objects through the browser's standard API. This is a very important step. Because the template must be parsable (compliant with specifications). This can be compared with most template systems, which are generally based on strings, not DOM elements.
2. Compilation of the DOM is done by calling the $comple() method. This method traverses the DOM and matches the directive. If the match is successful, it will be added to the directive list together with the corresponding DOM. As long as all directives associated with the specified DOM are identified, they will be sorted in priority and execute their compile() function in that order. Directive compile function has an opportunity to modify the DOM structure and is responsible for generating the parsing of the link() function. The $compile() method returns a combined linking function, which is a collection of linked functions returned by the compile function of the directive itself.
3. Connect the template to the scope through the linking function returned in the previous step. This in turn calls directive's own linking function, allowing them to register some listeners on the element, and create some watches with scope. The result in this is a two-way, instant binding between scope and DOM. When the scope changes, the DOM will get the corresponding response.
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);
4. Reasons behind the compile/link separation
At this time, you may wonder why the compilation process is divided into two steps: compile and link. To understand this, let's take a look at a real example (repeater)
Hello {{user}}, you have these actions: <ul> <li ng-repeat="action in user.actions">{{action.description}}</li> </ul>Simply put, the reason why we separate the two steps of compile and link is that sometimes the corresponding DOM structure needs to be changed after the model is changed, such as repeaters.
When the example above is compiled, the compiler will iterate through all nodes to find directive. {{user}} is an example of an interpolation directive. ngRepeat is another directive. But ngRepeat has a difficulty. It requires the ability to quickly create new li for every action in users.actions. This means that in order to satisfy the purpose of cloning li and embedding specific actions (here refers to one of the values of user's actions), it needs to keep a clean copy of the li element, which needs to be cloned and inserted into the ul element. But cloning the li element is not enough. Li also needs to be compiled so that its directive ({{action.descriptions}}) can be parsed in the correct scope. The original method generally simply inserts a copy of the li element and then compiles it. However, compiling copies of each li element will be slower, because the compilation process requires us to traverse the DOM node tree, find directives and run them. If we have a compile that needs to process 100 items through repeater, then we will be stuck with performance issues.
The solution to the problem is to break down the compilation process into two steps. The compile stage recognizes all directives and sorts them by priority, binding a specific scope with a specific li during the linking stage.
ngRepeat compiles individual lis separately to prevent the compilation process from falling into li elements. The compilation result of the li element is a directive linking function containing all the directives contained in the li element, ready to connect with the copy of the specific li element. At runtime, ngRepeat monitors the expression and is added as an item to an array of li elements copies, creating a new scope for the cloned li elements, and calling the link function corresponding to the copy.
Summarize:
1. Compile function - Compile functions are relatively rare in directives, because most directives only care about working with specified DOM elements, rather than changing the templates of the DOM elements (DOM itself and its internal structure). To optimize performance, some operations that can be shared by directive instances can be moved into the compile function.
2. Link function - Very few directives do not have link function. The link function allows directive to register a listener on the specified copy of the DOM element instance, or to copy specific content from the scope into the DOM.
5. Write a directive (simple version)
In this example, we will create a directive that displays the current time according to the input format.
<!DOCTYPE HTML><html lang="zh-cn" ng-app="TimeFormat"><head> <meta charset="UTF-8"> <title>time-format</title></head><body><div ng-controller="MyCtrl" id="main"> Date format: <input ng-model="format" type="text"/><hr/> <!--The following uses the attribute x-current-time to try the legal naming mentioned above~~current:time, current-time, current_time, data-current-time -_-!!! --> Current time is : <span x-current-time="format" id="myFormat"></span><br/> <button ng-click="remove()">remove the span</button></div><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript">angular.module("TimeFormat", []) //Register the directive factory method of "currentTime" in the TimeFormat application//As mentioned above, dependency injection can be written directly in the function parameters, injected here $timeout and dataFilter .directive("currentTime", function (dateFilter) { //This is the linking function mentioned above. (There is no need to add a compile function, why?...) return function (scope, element, attr) { var intervalId; //Update the text value of the corresponding element, that is, the update time function updateTime() { element.text(dateFilter(new Date(), scope.format)); } // Through watch, monitor the currentTime value of the span object (it is the model value of format, that is, the value of input!!) //This method only executes scope.$watch(attr.currentTime, function (value) { scope.format = value; updateTime(); }); //When the span is removed, cancel the update element.bind("$destroy", function () { clearInterval(intervalId); }); intervalId = setInterval(updateTime, 1000); }; }).controller("MyCtrl",function($scope,$rootScope) { $scope.format = "M/d/yy h:mm:ss a"; $scope.remove = function() { var oFormat = document.getElementById("myFormat"); if(oFormat) { angular.element(oFormat).remove();//Call remove in this way, the $destroy event can be triggered! ! ! I have tried it for a long time... } }; });</script></body></html>6. Write a directive (detailed version)
Below is a sample creation of a directive (directive object definition template). If you want to see the detailed list, please continue reading.
var myModule = angular.module(...); myModule.directive('directiveName', function factory(injectables) { var directiveDefinitionObject = { priority: 0, template: '<div></div>', templateUrl: 'directive.html', replace: false, transclude: false, restrict: 'A', scope: false, compile: function compile(tElement, tAttrs, transclude) { return { pre: function preLink(scope, iElement, iAttrs, controller) { ... }, post: function postLink(scope, iElement, iAttrs, controller) { ... } } }, link: function postLink(scope, iElement, iAttrs) { ... } }; return directiveDefinitionObject; });In most scenarios, we do not need precise control, so the above definition can be simplified. Defining each part of the template will be explained in the following chapter. In this chapter, we only focus on isomers of this skeleton that define the template (isomers of this skeleton, I don’t understand... Looking forward to everyone’s addition).
The first step in simplifying your code is to rely on default values. Therefore, the above code can be simplified to:
var myModule = angular.module(...); myModule.directive('directiveName', function factory(injectables) { var directiveDefinitionObject = { compile: function compile(tElement, tAttrs) { return function postLink(scope, iElement, iAttrs) { ... } } }; return directiveDefinitionObject; });Most directives only care about instances, not template conversion, so they can be further simplified (translated very hard... Looking forward to everyone's addition):
var myModule = angular.module(...); myModule.directive('directiveName', function factory(injectables) { return function postLink(scope, iElement, iAttrs) { ... } });7. Factory Method
The factory method is responsible for creating directives. It is used only once, just when the compiler first matches directive. You can do some initialization operations here. The factory method is executed through $injector.invoke, allowing it to comply with all injection declaration rules (rules of injection annotation), making it injectable.
8. Directive definition object description
Directive definition objects provide the compiler structure. The properties are as follows:
1.name - The name of the current scope. The default value can be used when registering (not filled in).
2.priority- When there are multiple directives defined in the same DOM element, sometimes it is necessary to clarify their execution order. This property is used to sort before the directive compile function call. If the priority is the same, the execution order is uncertain (after preliminary experiments, those with higher priority are executed first, and the same level is similar to the "post-binding" execution first". In addition, I was a little careless during the test. When defining the directive, a directive with the same name was defined twice, but the execution result found that both compiles or link functions will be executed).
3.terminal (last group) - If set to "true", it means that the current priority will become the directive of the last group of execution. If any directive is the same as the current priority, they will still execute, but the order is uncertain (although the order is uncertain, it is basically the same as the order of priority. After the current priority is executed, lower priority will not be executed again).
4.scope - If set to:
1).true - A new scope will be created for this directive. If there are multiple directives in the same element that require a new scope, it will still only create one scope. The new scope rules do not apply to the root template, so the root template tends to get a new scope.
2).{}(object hash) - A new, isolate scope will be created. The difference between "isolate" scope and general scope is that it is not inherited from the parent scope through prototypes. This is very helpful for creating reusable components and can effectively prevent reading or modifying data from the parent scope. This independent scope creates an object hash with a set of local scope properties derived from the parent scope. These local properties are useful for aliasing values for templates -_-!. Locals definition is a hash of local scope property to its source #&)$&@#)($&@#_):
3).@ or @attr - Create a local scope property to the DOM property. Because the property value is always String type, this value always returns a string. If the attribute name is not specified via @attr, the local name will always be with the name of the DOM attribute. For example, <widget my-attr=”hello {{name}}">, the scope of widget is defined as: {localName:'@myAttr'}. Then, the localName of the widget scope property will map the real value converted by "hello {{name}}". After the name attribute value changes, the localName attribute of widget scope will also change accordingly (only one-way, different from the "=" below). The name attribute is read in the parent scope (not the component scope)
4).= or =expression (maybe attr here) - Set a bidirectional binding between the local scope attribute and the parent scope attribute. If the attr name is not specified, the local name will be consistent with the attribute name. For example, <widget my-attr=”parentModel”>, the scope defined by widget is: {localModel:'=myAttr'}, then the widget scope property "localName" will map the parent scope's "parentModel". If any changes occur in parentModel, localModel will also change, and vice versa. (Bi-way binding)
5).& or&attr - Provides a way to execute an expression in the parent scope context. If the attr name is not specified, the local name will be consistent with the attribute name. For example, <widget my-attr=”count = count + value”>, the scope of widget is defined as: {localFn:'increment()'}, then isolate scope property "localFn" will point to a function wrapped with an increment() expression. Generally speaking, we want to pass data from the isolate scope to the parent scope through an expression. This can be done by passing a map of the key value of a local variable into the wrapper function of the expression. For example, if the expression is increment(amount), then we can call localFn through localFn({amount:22}) to specify the value of amount (the above example really doesn't understand, & where did you go?).
5.controller - controller constructor. The controller will initialize before the pre-linking step and allow other directives to share via the required with the specified name (see the require property below). This will allow directives to communicate with each other and enhance mutual behavior. The controller injects the following local objects by default:
1).$scope - scope combined with the current element
2).$element - Current element
3).$attrs - The attribute object of the current element
4).$transclude - A transpose linking function pre-bound to the current transpose scope :function(cloneLinkingFn). (A transclude linking function pre-bound to the correct translation scope)
6.require - Request another controller to pass it into the current directive linking function. require that the name of a direct controller is passed in. If the controller corresponding to this name cannot be found, an error will be thrown. The name can be prefixed with the following:
1).? - Do not throw exceptions. This makes this dependency an option.
2).^ - A controller that allows searching for parent elements
7.restrict - A string of a subset of EACM, which limits directive to the specified declaration method. If omitted, directive will only allow declarations via attributes:
1) E - Element name: <my-directive></my-directive>
2).A - Attribute name: <div my-directive=”exp”></div>
3). C - class name: <div class="my-directive:exp;"></div>
4).M - Comment: <!-- directive: my-directive exp -->
8.template - If replace is true, replace the template content with the current HTML element, and migrate the original element's attributes and class; if false, the template element is treated as a child element of the current element. For more information, please check out the "Creating Widgets" chapter (where...Creating Components are available...)
9.templateUrl - is basically the same as template, but the template is loaded through the specified url. Because the template loading is asynchronous, compilation and linking will be paused and will be executed after loading.
10.replace - If set to true, the template will replace the current element instead of being added to the current element as a child element. (Note: When true, the template must have a root node)
11.transclude - Compile the content of an element so that it can be used by directive. Required (in the template) to be used (referenced). The advantage of transclusion is that linking function can obtain a translation function that is pre-bound to the current scope. Generally, create a widget and create an isolate scope. Translation is not a child, but a brother of the isolate scope. This will make the widget have a private state and the transclusion will be bound to the parent (pre-isolate) scope. (I don't understand the above paragraph. But in actual experiments, if myDirective is called through <any my-directive>{{name}}</any my-directive> and the transclude is set to true or a string and the template contains <sometag ng-transclude>, the compilation result of {{name}} will be inserted into the content of sometag. If any content is not wrapped by the tag, then there will be an extra span in the sometag. If there is something else wrapped, it will remain the same. But if the transclude is set to 'element', the overall content of any will appear in the sometag and be wrapped by p)
1).true - Convert the content of this directive. (In this sense, it is to directly compile the content and move it into the designated place)
2).'element' - Converts the entire element, including other directives with lower priority. (For example, after compiling the entire content, it is treated as a whole (wrapped p outside) and inserted into the specified place)
12.compile - Here is the compile function, which will be explained in detail in the following chapters
13.link - Here is the link function, which will be explained in detail in the following chapter. This property is only used if the compile property is not defined.
9. Compile function
function compile ( tElement, tAttrs, transclude) { … }
compile function is used to handle conversion of DOM templates. Since most directives do not require conversion templates, compile will not be used frequently. Directive that requires compile function, generally those that need to convert DOM templates (such as ngRepeat), or those that need to load content asynchronously (such as ngView). The compile function has the following parameters:
1.tElement - The template element uses the current directive element. It is safe to just do template conversion under the current element or the current element child element.
2.tAttrs - Template Attributes - Standardized attributes, declared in the current element, can be shared among various directives. For details, please see the Attributes chapter
3.transclude A linking function for conversion: function(scope,cloneLinking).
Note: If the template has been cloned, the template instance and the link instance cannot be the same object. To do this, it is not safe to do anything other than DOM conversion in the compile function, which will be applied to all clones. In particular, the registration operation of the DOM event listener should be performed in the linking function, not the compile function.
compile function can have a return value, and the type can be function or object.
1. Return function is usually used when compile function is not required (empty), which is equivalent to registering a linking function through link (directly defines the attributes of the template).
2. Returns an object containing pre and post properties - allows us to control when linking function is called during linking phase. For details, please see the following chapters about pre-linking and post-linking functions.
10. Linking function
function link( scope, iElement, iAttrs, controller) { … }
The link function is responsible for registering the DOM event listener, and can also perform DOM update operations. The link function will be executed after the template cloning operation is completed. Most of the logic of directive is stored here.
1.scope - Scope - is used to register watches (http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch).
2.iElement - Element instance - Element used by directive. It is safe to operate on child elements in the postLink function. Because the child elements have been linked (connected to the model?!).
3.iAttrs - Attribute instance - The attribute list of the standard current element. Shared between all directive linking functions.
4.controller - controller instance - If one of the controllers is defined in the directive of the current element, you can get an instance of the controller here. This controller is shared among all directives, allowing each directive to treat the controller as a communication channel between them.
Pre-link function
Execute before child element linked. It is not safe to do DOM conversion here, because compiler's linking function may not locate the correct elements when linking.
Post-link function
Execute after child element linked. It is safe to perform DOM conversion here.
11. Attributes
attribute object - as arguments in link() or compile() - is a way to access the following:
1. Standardized attribute names: Because directive, such as ngBind, it can be manifested in many forms, such as "ng:bind", "x-ng-bind"... This attribute object allows us to access attributes through standard naming (camel).
2. Communication between directives: All directives share an attribute object instance, so that directives can communicate between directives through attribute objects.
3. Support interpolation: The interpolation attribute is assigned to an attribute object, allowing other directives to read the interpolated value.
4. Observe interpolated attributes: observe changes in attribute values through attr.$observe, including interpolation (for example, src=”{{bar}}"). Not only is it very effective, but it is also the only way to simply obtain the real value. Because during the linking stage, the interpolation has not been assigned (replaced with the real value), so when accessing it at this time, the result is undefined.
<!DOCTYPE HTML><html lang="zh-cn" ng-app="DirectiveProperty"><head> <meta charset="UTF-8"> <title>directive-attribute-test</title> <style type="text/css"> .ng-cloak { display: none; } </style></head><body ng-controller="MyCtrl"><input type="text" ng-model="name" value="myName"/><p my-attr="123" directive-p2 attr-dd="{{name}}"></p><script src="../angular-1.0.1.js" type="text/javascript"></script><script type="text/javascript"> var app = angular.module("DirectiveProperty", []); app.controller("MyCtrl", function ($scope) { $scope.name = "my little dada"; }); var directiveP2 = app.directive("directiveP2", function () { return { link:function postLink(scope,lEle,lAttr) { console.log("myAttr:" + lAttr.myAttr);//123 console.log("myAttr:" + lAttr.attrDd);//undefined lAttr.$observe('attrDd', function(value) { console.log('attrDd has changed value to ' + value); //my little dada //In addition, what else can I get this value... ¥&()@#&¥(@# }); } }; }); });</script></body></html>12. Understand transclusion and scope
We often need some reusable components. Here is a pseudo-code showing how a simple dialog component can work.
<button ng-click="show=true">show</button> <dialog visible="show" on-cancel="show = false" on-ok="show = false; doSomething()"> Body goes here: {{username}} is {{title}}.</dialog>Clicking the "show" button will open the dialog. The dialog has a title that is bound to the data "username", and there is also a paragraph that we want to place inside the dialog.
The following is a template definition written for the dialog:
<div ng-show="show()"> <h3>{{title}}</h3> <div ng-transclude></div> <div> <button ng-click="onOk()">Save changes</button> <button ng-click="onCancel()">Close</button> </div></div>This will not render correctly unless we do some special treatment on scope.
The first problem we need to solve is that the dialog template expects the title to be defined and will be bound to username when initialized. In addition, the button requires two functions onOk and onCancel to appear in the scope. This limits the usefulness of the widget..). To solve the mapping problem, the local variables expected by the template are created by the following local methods (locals, which is estimated to be the scope in the directive definition template):
scope :{ title: 'bind', // set up title to accept data-binding onOk: 'expression', // create a delegate onOk function onCancel: 'expression', // create a delegate onCancel function show: 'accessor' // create a getter/setter function for visibility.}Creating local properties in the control scope brings two problems:
1. isolation (attribute isolation?) - If the user forgets to set the element attribute title in the control template, the title will be bound to the attribute "title" of the ancestor scope (if any). This is unpredictable and undesirable.
2. transclusion - translated DOM can view the locals (isolate scope?) of the control. locals will override the properties that really need to be bound in the transclusion. In our example, the title property in the plugin destroys the title property of the transclusion.
To solve this problem of lack of attribute isolation, we need to define an isolated scope for this directive. isoloted scope is not inherited from the prototype from the child scope (why is it child scope? Isn't it parent scope?) so we don't need to worry about attribute conflict issues (as the current scope's brother).
However, the isolated scope brings a new problem: if a translated DOM is a child of the widget isolated scope then it will not be able to bind to anything. Therefore, the translated scope is a child scope of the original scope created before the control creates the isolated scope for the local property. The translated and the isolated scope belong to the sibling node (in the scope tree).
This may seem a bit unexpectedly complicated, but doing so brings at least surprises to control users and control developers. (The problem was solved)
Therefore, the final directive definition is roughly as follows:
transclude:true,scope :{ title: 'bind', // set up title to accept data-binding onOk: 'expression', // create a delegate onOk function onCancel: 'expression', // create a delegate onCancel function show: 'accessor' // create a getter/setter function for visibility. // I have tried this, but it failed... please continue to read}I've tried to piece together the above code into a complete example. If you copy directly, the expected results will not be achieved. But after a little modification, the plug-in can be run.
<!DOCTYPE html><html ng-app="Dialog"><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>directive-dialog</title> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"> <script src="../angular.js" type="text/javascript"></script></head><body> <div ng-controller="MyCtrl"> <button ng-click="show=true">show</button> <dialog visible="{{show}}" on-cancel="show=false;" on-ok="show=false;methodInParentScope();"> <!--The above on-cancel and on-ok are referenced by & in the directive isoloate scope. If the expression contains a function, then you need to bind the function in the parent scope (currently MyCtrl scope) --> Body goes here: username:{{username}} , title:{{title}}. <ul> <!--You can also play like this here~names is parent scope --> <li ng-repeat="name in names">{{name}}</li> </ul> </dialog> </div> <script type="text/javascript"> var myModule = angular.module("Dialog", []); myModule.controller("MyCtrl", function ($scope) { $scope.names = ["name1", "name2", "name3"]; $scope.show = false; $scope.username = "Lclao"; $scope.title = "parent title"; $scope.methodInParentScope = function() { alert("Remember.. The thing defined in scope is played in the parent scope!!!"); }; }); myModule.directive('dialog', function factory() { return { priority:100, template:['<div ng-show="visible">', ' <h3>{{title}}</h3>', ' <div ng-transclude></div>', ' <div>', ' <button ng-click="onOk()">OK</button>', ' <button ng-click="onCancel()">Close</button>', ' </div>', ' </div>', ' </div>'].join(""), replace:false, transclude: true, restrict:'E', scope:{ title:"@",//quote the value of the title attribute of the dialog tag onOk:"&",//reference the content of the on-ok attribute of the dialog tag in the form of wrapper function onCancel:"&",//use wrapper function form references the content of the on-cancel property of the dialog tag visible: "@"//refers to the value of the visible property of the dialog tag} }; }); </script></body></html>13. Creating Components
We usually expect to replace the directive through a complex DOM structure (the element is located? The purpose is probably to make the directive internal complex points, which look awesome points @_@). This makes directive a shortcut to building applications using reusable components.
Here is an example of a reusable component:
<!DOCTYPE html><html ng-app="ZippyModule"><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>ZippyModule</title> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"> <style type="text/css"> .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> <script src="../angular.js" type="text/javascript"></script></head><body> <div ng-controller="MyCtrl"> Title: <input ng-model="title" type="text"><br/> Text: <textarea ng-model="text"></textarea> <hr/> <div zippy-title="Details: {{title}}...">{{text}}</div> </div> <script type="text/javascript"> var myModule = angular.module("ZippyModule", []); myModule.controller("MyCtrl", function ($scope) { $scope.title = "Here is the title"; $scope.text = "Here is the content... "; }); myModule.directive('zippy', function () { return { template: '<div>' + ' <div>{{title}}</div>' +//This title belongs to the property of the current direct isolate scope ' <div ng-transclude></div>' + //What is here, what is obtained is the property of the parent scope '</div>', replace:true, transclude: true, restrict:'C', scope:{ title:"@zippyTitle"//Bind the zippy-title attribute on the directive element}, link:function(scope,element,attrs) { var title = angular.element(element.children()[0]), opened = false; title.bind("click", toogle); element.addClass("closed"); function toogle() { opened = !opened; element.removeClass(opened ? "closed" : "opened"); element.addClass(opened ? "opened" : "closed"); } } }; }); </script></body></html>