The examples in this article describe how ANGULARJS creates custom directives. Share to everyone for your reference, specific as follows:
This is a translation from the instructions of the angular developer. Mainly for developers who are already familiar with the angular development Foundation. This document explains the circumstances in which you need to create your own instructions and how to create them.
What is an instruction?
At a high level, an instruction is a description of the angular $compile service, and when a particular tag (attribute, element name, or annotation) appears in the DOM, it lets the compiler append the specified behavior to the DOM.
This process is very simple. Angular internal commands such as Ngbind, Ngview, as you create controllers and services, you can create your own instructions. When the angular is started, the angular compiler analyzes HTML to match the instructions, which allows the instruction to register the behavior or change the DOM.
Matching instructions
Before writing instructions, we first need to know how angular is matched to an instruction, in the following example we say the INPUT element matches the Ngmodel instruction.
The following method will also match the Ngmodel:
<input data-ng:model= "foo" >
Angular standardizes the tag name and property name of an element to determine which element matches which instruction. We refer to instructions (such as Ngmodel) by using the normalized hump-style name in JS. The '-' delimited attribute names are often used in HTML to invoke instructions (for example, Ng-model).
Normalized processing process:
-Remove the X-and data-prefixes above the element or attribute
-Convert ': ', '-' and ' _-' in the form of a camel-style spell
The following example shows a different way of matching to the ngbind instruction
<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/>
Best Practice: First Use the '-' format name (for example, ng-bind matching ngbind). If you want to pass in the HTML validation tool, you can use the ' data-' prefix (such as Data-ng-bind). Other formats are named for reasons that are left behind by history and avoid using them.
$compile Service can match instructions based on the element's name, attribute name, class name, and annotation
All angular internal instructions match the property name, tag name, comment, or class name. The following different ways can be resolved to
<my-dir></my-dir>
<span my-dir= "exp" ></span>
<!--directive:my-dir exp-->
<span class= "MY-DIR:EXP;" ></span>
Best Practice: Use directives in the form of signature and attribute names. It's easier to understand which element the specified element matches.
Best Practice: Annotations are often used in the DOM API limit to create instructions that span multiple elements, such as table elements, which limit repeated nesting, so that you need to use annotations. In the Angularjs 1.2 version, the problem is addressed by using Ng-repeat-start and ng-repeat-end as a better solution. This approach is recommended, where possible.
Binding of text and properties
During compilation, the compiler uses the $interpolate service to detect whether the matching text and property values contain inline expressions. These expressions are registered as watches and can be updated when the digest loop is used.
<a ng-href= "Img/{{username}}.jpg" >hello {{username}}!</a>
Binding of the Ngattr property
The browser will sometimes be very picky about what it considers to be a legitimate attribute value (that is, the attribute of some elements is not assignable, otherwise it will be an error).
Like what:
<svg>
<circle cx= "{{cx}}" ></circle>
</svg>
When you use this notation, we will find that the console error error:invalid value for attribute cx= ' {{cx}} '. This is due to the limitations of the SVG DOM API, which you cannot simply write as cx= "{{cx}}".
Using NG-ATTR-CX can solve this problem
If a bound property uses the ngattr prefix (or ng-attr), then the binding will be applied to the corresponding not-prefixed attribute, which allows you to bind to the attribute (such as the circle[cx of the SVG element) that needs to be processed immediately by the browser.
So, we can write this to fix the above problem:
<svg>
<circle ng-attr-cx= "{{cx}}" ></circle>
</svg>
Creating directives
First we'll talk about the API for registering instructions, like Controller, where instructions are registered on a module, and the instructions are registered through the Module.directive API. Module.directive accepts a normalized name and factory function that returns an object that contains a different configuration that tells the $compile service how to proceed with the next step.
The factory function is invoked only once when the compiler first matches the instruction. You typically perform initialization work in a factory function. The function uses the $injector.invoke invocation, so it can be controller like a dependency injection.
Best Practice: Returns a defined object first, rather than returning a function.
Next, we'll look at some common examples before delving into the rationale and compilation of different configuration items.
Best Practice: To avoid some future standard naming conflicts, it's a good idea to prefix your own directives, such as creating a <carousel> directive, which may create conflicts, and join HTML7 to introduce the same elements. It is recommended that you use a prefix of two or three words (such as Btfcarousel), and you cannot use NG or any other prefix that may conflict with future versions of angular.
In the following example, we use the My prefix uniformly.
Instructions for template extensions
When you have a large number of templates representing customer information. This template is repeated many times in your code, and when you change a place, you have to make changes at the same time in other places, and then you need to use instructions to simplify your template.
We're going to create a directive that's simple when the static template replaces its content.
<div ng-controller= "Ctrl" >
<div my-customer></div>
</div>
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}} '
};
};
Note that we have some bindings here, $compile compile the link <div my-customer> </div>, it will match the instruction of the child element. This means you can assemble a few commands. In the following example you will see how to do this.
In this example, we write the template directly in the template configuration item, but as the size of the template increases, it is not elegant.
Best Practice: Unless your template is very small, better to split into a separate HMTL file, and then use the Templateurl option to load.
If you are familiar with Nginclude,templateurl it is very similar to it. Now we use the Templateurl method to rewrite the above example:
<div ng-controller= "Ctrl" >
<div my-customer></div>
</div>
Js:
Angular.module (' docstemplateurldirective ', [])
. Controller (' Ctrl ', function ($scope) {
$scope. Customer = {
name: ' Naomi ', address
: ' 1600 Amphitheatre '
}
. directive (' MyCustomer ', function () {return
{
templateurl: ' my-customer.html '
};
});
My-customer.html
Name: {{customer.name}} Address: {{customer.address}}
Very good, but if we want our instructions to match the tag name <my-customer>? If we simply put <my-customer> elements on top of the HMTL, we will find no effect.
Note: When you create a directive, the only way to use attributes is by default. In order to create an instruction that can be triggered by an element name, you need to use a restrict configuration.
The Restrict configuration can be set as follows:
-' A ' matches only the attribute name
-' E ' matches only the element name
-' AE ' can be matched to a property name or element name
So we can use restrict: ' E ' to configure our instructions.
<div ng-controller= "Ctrl" >
<div my-customer></div>
</div>
Js
Angular.module (' docstemplateurldirective ', [])
. Controller (' Ctrl ', function ($scope) {
$scope. Customer = {
name: ' Naomi ', address
: ' 1600 Amphitheatre '
}
. directive (' MyCustomer ', function () {return
{
restrict: ' E ',
templateurl: ' my-customer.html '
};
});
My-customer.html
Name: {{customer.name}} Address: {{customer.address}}
Note: When do I use an attribute name or an element name? When you create a component that contains your own template, you need to use the element name, and if you are simply adding functionality to an existing element, use the property name.
Using element names as MyCustomer directives is the right decision, because instead of decorating the element with some ' customer ' behavior, you define an element with its own behavior as the customer component.
Scope of quarantine directives
Our MyCustomer command has been very good, but it has a fatal flaw that we can only use once in a given scope.
What it does now is that we create a new controller for it each time we reuse the instruction.
<div ng-controller= "Naomictrl" >
<my-customer></my-customer>
</div>
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 '
};
});
My-customer.html
Name: {{customer.name}} Address: {{customer.address}}
This is obviously not a good solution.
What we want to do is to be able to isolate the scope of the instruction from the external scope, and then map the external scope to the scope within the instruction. You can do this by creating a isolate scope. In this case, we use the scope configuration of the instruction.
<div ng-controller= "Ctrl" >
<my-customer customer= "Naomi" ></my-customer>
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 '
};
My-customer.html
Name: {{customer.name}} Address: {{customer.address}}
First look at HMTL, first <my-customer> bind the internal scope of the customer to Naomi. We have already defined this Naomi in the controller. The second is to bind the customer to the Igor.
Now look at how scope is configured.
//...
Scope: {
customer: ' =customer '
},
//...
The property name (customer) is the variable name isolated scope on the MyCustomer directive. Its value (=customer) tells the compiler to bind to the Customer property.
Note: The ' =attr ' attribute name in the directive scope configuration is the name that is normalized, such as binding <div bind-to-this= "thing" to the attribute in the "=bindtothis".
As with the name of the property name and the value you want to bind, you can use this shortcut syntax:
//...
Scope: {
//Same as ' =customer '
customer: ' = '
},
//...
Another use of isolated scope is that you can bind different data to the scope within the directive.
In our example, we can add another attribute vojta to our scope and then access it in our instruction template.
<div ng-controller= "Ctrl" >
<my-customer customer= "Naomi" ></my-customer>
</div>
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 '
};
My-customer-plus-vojta.html
Name: {{customer.name}} Address: {{customer.address}}
Note that {{Vojta.name}} and {{vojta.address}} are empty, which means they are undefined, although we have defined VOJTA in the controller, but we cannot access it within the instruction.
As its name implies, the isolate scope of the directive isolates everything except the data model that you add to the scope: {} object. This is useful for creating a reusable component because it prevents other things from changing the state of your data model other than the data model you want to pass in.
Note: Normally, the scope is inherited from the parent scope by the prototype. But isolate scope does not have such an inheritance.
Best Practice: When you want to make your component reusable within your application, use the scope configuration to create a isolate scopes
Create an instruction to manipulate the DOM
In this example, we will create an instruction that displays the current time, updating the DOM once per second to correctly display the current time.
Instruction modification Dom is usually in the link configuration, the link option accepts a function functions link (scope,element,attrs) {...} with the following label which
-scope is angular scope object
-element instruction matching Jqlite encapsulated elements (angular internal implementation of class jquery libraries)
-attrs is an object with normalized attribute names and corresponding values
In our link function, we update the display time once per second, or when the user changes the time format string for the specified binding. We also need to remove the timer to avoid introducing a memory leak when the instruction is deleted.
<div ng-controller= "Ctrl2" >
Date Format: <input ng-model= "format" >
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 ();
}
return {
link:link
};
};
There are a lot of things to note here, like the Module.controller API, where function parameters in Module.directive are dependency injection, so we can use $timeout and datafilter services within the link function.
We registered an event Element.on (' $destroy ', ...), what triggered the $destory event?
ANGULARJS triggers certain events, and when a angular DOM element is removed, it triggers a $destory event, and similarly, when a angular scope is removed, it broadcasts down $ Destory event to all listening scopes.
By listening to events, you can remove event listeners that may cause memory leaks, listeners registered on elements and scopes are automatically cleaned out when they are removed, but if you register an event on a service or a DOM node that has not been deleted, you must manually clean it, or there will be a risk of a memory leak.
Best Practice: You should do some cleanup when performing the removal, and you can use Element.on (' $destroy ', ...) or Scope.on (' $destroy ', ...) To run the Unbind function.
Create instructions for wrapping other elements
We have now implemented the use of isolate scopes to pass the data model into the instruction. But sometimes we need to be able to pass an entire template rather than a string or object. Let's illustrate by creating the ' dialog box ' component. This ' dialog box ' component should be able to wrap any content.
To implement this, we use the transclude configuration
<div ng-controller= "Ctrl" >
<my-dialog>check out the contents, {{name}}!</my-dialog>
</ Div>
Js
Angular.module (' docstransclusiondirective ', [])
. Controller (' Ctrl ', function ($scope) {
$scope. Name = ' Tobias ';
})
. directive (' Mydialog ', function () {return
{
restrict: ' E ',
transclude:true,
templateurl: ' My-dialog.html '
};
My-dialog.html
<div class= "alert" ng-transclude>
</div>
What's this transclude configuration for? Transclude enables the content of instructions with this configuration to access scopes outside the directive.
Taking into account the following example, we added a link function where we redefined the value of the name attribute to Jeff, so what is the value of this {{name}} now?
<div ng-controller= "Ctrl" >
<my-dialog>check out the contents, {{name}}!</my-dialog>
</div>
Js
Angular.module (' docstransclusiondirective ', [])
. 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 ';};
});
My-dialog.html
<div class= "alert" ng-transclude>
</div>
Generally, we think that {{name}} will be parsed as Jeff, but here we see that the {{name}} In this example is parsed into Tobias.
The transclude configuration changes the way that the instructions are nested, and he makes the contents of the instruction subject to the scope of any instruction outside, not the internal scope. To achieve this, it gives the instruction content an opportunity to access the external scope once.
Such behavior is very meaningful for the instruction of parcel content. Because if you don't, you have to pass in each of the data models you need to use separately. If you need to pass in every data model that you want to use, you won't be able to adapt to a variety of different content.
Best Practice: Use transclude:true only if you want to create an instruction that wraps any content.
Next, we add a button to the ' dialog box ' component that allows users to bind their own defined behavior using instructions.
<div ng-controller= "Ctrl" >
<my-dialog ng-hide= "Dialogishidden" on-close= "Dialogishidden = true" >
Check out the contents, {{name}}!
</my-dialog>
</div>
Js
Angular.module (' Docsisofnbindexample ', [])
. Controller (' Ctrl ', function ($scope, $timeout) {
$scope. Name = ' Tobias ';
$scope. Hidedialog = function () {
$scope. Dialogishidden = true;
$timeout (function () {
$scope. Dialogishidden = false;};
directive (' Mydialog ', function () {return
{
restrict: ' E ',
transclude:true,
scope: {
' Close ': ' &onclose '
},
templateurl: ' my-dialog-close.html '
};
My-dialog-close.html
<div class= "alert" >
<a href class= "close" ng-click= "Close ()" >x</a>
<div Ng-transclude></div>
</div>
We want to run the function we pass in by invoking the scope of the instruction, but this function is the context in which the definition is run (JS is usually the case).
Previously we saw how scope was configured to use ' =prop ', but in the example above, we used ' &prop ', ' & ' bindings to open a function to isolated scope, allowing isolated scope to call it, While maintaining the scope of the original function (the scope here refers to $scope). So when a user clicks on X, the CTRL controller's close function is run.
Best Practice: When your instructions want to open an API to bind specific behaviors, use ' &prop ' in the scope configuration.
Create an instruction to add an event listener
Previously, we used the link function to create an instruction to manipulate the DOM element, and based on the example above, we created an instruction to add an event listener to the element.
For example, what if we want to create an element that allows the user to drag and drop?
Drag ME
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);
}
}
);
Create instructions to communicate with each other
You can assemble any instruction by using instructions in the template.
Sometimes, you want an instruction to create from another command.
Imagine you want a container with a tab, and the contents of the container correspond to the active tab.
<my-tabs>
<my-pane title= "Hello" >
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 '
};
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>
My-pane.html
<div class= "Tab-pane" ng-show= "selected" Ng-transclude>
</div>
The Mypane directive has a require: ' ^mytabs ' configuration, when the instruction uses this configuration, $compile the service is called the mytabs instruction and obtains its controller instance, if did not find, will throw an error. The ' ^ ' prefix means that the instruction searches for the controller (without the ' ^ ' prefix) above its parent element, and the instruction defaults to searching for the specified instruction on its own element.
Where does the Mytabs controller come from? By using the controller configuration, you can specify a controller for the instruction, and in the example above Mytab use this configuration. Like Ngcontroller, this configuration is a controller for the template of the instruction.
Looking at our Mypane's definition, note the last parameter of the link function: Tabctrl, when an instruction contains another instruction (via require), it receives the controller instance of the instruction as the fourth parameter of the link function, using this, Mypane can call the Mytabs addpane function.
Savvy readers may want to know the difference between link and controller, the most basic difference is that the controller is open an API (that is, this controller instance can be read by other instances), the link function can interact with the controller through the require.
Best Practice: Use the controller when you want to open an API to other instructions, otherwise use the link function.
Summarize
Here we explain a few of the main use cases of instructions. Each one can be a good starting point for you to create your own directives.
If you want to learn more about the process of compiling, you can view compiler guide related content
The $compile API page has directive specific explanations for each configuration item, which you can refer to.
I hope this article will help you to Angularjs program design.