Instruction (directives) is the most important part of all ANGULARJS applications. Although Angularjs has provided very rich instructions, it is often necessary to create application-specific directives. This tutorial will tell you how to customize instructions and how to use them in real projects. At the end of this article (Part II), I'll instruct you on how to use the angular directive to create a simple Notepad application.
Overview
A directive is used to introduce new HTML syntax. An instruction is a token on a DOM element that has a specific behavior for that element. For example, static HTML does not know how to create and present a date selector control. To allow HTML to recognize this syntax, we need to use instructions. Directives can be used to create an element that supports date selection. We will introduce step-by-step how this is achieved. If you have written ANGULARJS applications, then you must have used the instructions, whether you realize it or not. You must have used simple instructions, such as Ng-mode, Ng-repeat, ng-show, etc. These directives give DOM element-specific behavior. For example, ng-repeat repeats a particular element, Ng-show conditionally displays an element. If you want an element to support dragging, you also need to create a command to implement it. The basic idea behind the directive is simple. It enables HTML to have real interactivity by listening to the element binding events or changing the DOM.
jquery Perspective
Imagine using jquery to create a date selector. First, we add an ordinary input box to the HTML and then call $ (element) through jquery. Datapicker () to convert it into a date selector. But think about it. When a designer comes to check the HTML tag, can he or she immediately guess what the field actually represents? Is this just a simple input box, or a date selector? You need to look at the jquery code to determine this. The angular approach is to use a single instruction to extend the HTML. So, a date selector's instruction can be in the following form:
Or is this:
This is a much more straightforward and clear way to create a UI build. You can easily see what this is all about by looking at the elements.
To create a custom directive:
A angular instruction can have the following four representations: 1. A new HTML Element (<data-picker></data-picker>) 2. The attributes of the element (<input type= "text" data-picker/>) 3. CSS Class (<input type= "text" class= "Data-picker"/>) 4. Note (<!–directive:data-picker–>) Of course, we can control the representation of our instructions in HTML. Now let's take a look at a typical instruction in Angularjs. The instructions are registered in the same way as controller, but it returns a simple object (instruction definition object) that has the command configuration attribute. The following code is a simple Hello world directive.
var app = Angular.module (' MyApp ', []);
App.directive (' HelloWorld ', function () {return
{
restrict: ' AE ',
replace: ' true ',
Template: '
In the above code, the App.directive () method registers a new instruction in the module. The first parameter of this method is the name of the instruction. The second parameter is a function that returns the instruction definition object. If your instructions are dependent on other objects or services, such as $rootScope, $http, or $compile, they can be injected at this time. This instruction is used in HTML as an element, as follows:
Or, use it as a property:
<div hello-world></div>
//or
<div hello:world/>
If you want to conform to the HTML5 specification, you can add an X or data-prefix to the element before it. So the following tag will also match the HelloWorld directive:
<div data-hello-world></div>
//or
<div x-hello-world></div>
Note: When matching instructions, angular will remove the X-or data-prefixes in the name of the element or attribute. The-or: concatenated strings are then converted to hump (CamelCase) representations, which are then matched with registered instructions. This is why we use the helloWorld instruction in HTML in Hello-world way. In fact, this is related to HTML's case-insensitive for tags and attributes. Although the above instructions only achieve static text display, but here are some interesting points worth digging. We used three attributes to configure instructions during the instruction definition process. Let's explain their role.
- restrict– This property is used to specify how instructions are used in HTML (remember the four representations of instructions that were previously said). In the example above, we used the ' AE '. So this instruction can be used as a new HTML element or attribute. If you want to allow instructions to be used as class, we set the restrict to ' AEC '.
- template– This property sets the HTML markup that is generated after the instruction is angular compiled and linked (link). This attribute value is not necessarily a simple string. Template can be very complex and often contain other directives, as well as expressions ({{}}). More often than not, you may see templateurl instead of template. So ideally, you should put the template in a particular HTML file, and then point the Templateurl attribute to it.
- Replace– This property indicates whether the generated HTML content will replace the HTML element that defines this directive. In our example, we use our instructions in the
Open this plunker, in "Hello world!!" Right-click the element content to understand this more vividly.
Link functions and scope
The template generated by the directive does not have much meaning unless it is compiled under a specific scope. By default, the directive does not create a new child scope. More, it uses the parent scope. In other words, if the instruction exists under a controller, it will use the scope of the controller. How to use scope, we need to use a function called link. It is configured by the directive to define the link property in the object. Let's change our HelloWorld instructions, when the user enters a name for a color in an input box, the background color of the Hello world text changes automatically. Also, when the user clicks on the Hello World text, the background color changes back to white. The corresponding HTML tags are as follows:
<body ng-controller= "Mainctrl" >
<input type= "text" ng-model= "color" placeholder= "Enter a Color"/>
The revised HelloWorld instructions are as follows:
App.directive (' HelloWorld ', function () {return
{
restrict: ' AE ',
replace:true,
Template: ' <p Style= "Background-color:{{color}}" >hello World,
link:function (scope, Elem, attrs) {
elem.bind (' click ', function () {
elem.css (' background-color ', ' White ');
Scope. $apply (function () {
Scope.color = "white";
});
};
Elem.bind (' MouseOver ', function () {
elem.css (' cursor ', ' pointer ');
});
}
;}
;
We note the LINK function in the directive definition. It has three parameters:
- Scope of the scope– directive. In our case, the scope of the instruction is the scope of the parent controller.
- The Jqlite (subset of jquery) of the elem– directive wraps the DOM element. If you introduced jquery before introducing Angularjs, then this element is the jquery element, not the jqlite element. Since this element has been jquery/jqlite packaged, we do not need to use $ () to wrap the DOM operation.
- attr– a normalized parameter object that contains the attributes of the element on which the instruction resides. For example, you add attributes to an HTML element: You can use it through Attrs.someattribute in the link function.
The link function is used primarily to add event sniffing to DOM elements, monitor model property changes, and update the DOM. In the instruction code snippet above, we added two events, click, and MouseOver. The click handler functions to reset the <p> background color, while the mouseover handler changes the mouse to pointer. There is an expression {{color}} in the template that is used to change the background color of the Hello world text when the color in the parent scope changes. This plunker demonstrates these concepts.
Compile function
The compile function is used to make some DOM modifications before the link function is executed. It receives the following parameters:
The element where the telement– directive is located
Standardized list of parameters given on the attrs– element
Note that the compile function does not have access to scope and must return a link function. However, if you do not set the compile function, you can configure the link function properly (with compile, you cannot return it by compile with the Link,link function). The compile function can be written in the following form:
App.directive (' Test ', function () {return
{
compile:function (telem,attrs) {
//do optional DOM Transformation here return
function (scope,elem,attrs) {
//linking function here
}
;}};
In most cases, you just need to use the link function. This is because most of the instructions only need to consider registering the event listener, monitoring the model, and updating the DOM, which can be done in the link function. However, for instructions like ng-repeat, you need to clone and repeat DOM elements multiple times, which are done by the compile function before the link function executes. This brings up a problem, why we need two separate functions to complete the build process, why not just use one? To answer this question, we need to understand how the instructions are compiled in the angular!
How the instructions are compiled
When the application boot starts, angular begins to traverse the DOM element using the $compile service. This service searches for instructions in tagged text based on registered directives. Once all the instructions have been identified, the angular executes their compile method. As mentioned earlier, the Compile method returns a link function that is added to the list of link functions that will be executed later. This is called the compile phase. If a command needs to be cloned many times (such as Ng-repeat), the compile function is executed only once in the compile phase, but the link function is executed for each replicated instance. So separate processing, let us have a certain improvement in performance. This also explains why the scope object cannot be accessed in the compile function. After the compile phase, the link (linking) phase begins. At this stage, all the collected link functions will be executed by one by one. The template created by the directive is parsed and processed under the correct scope, and then returns the true DOM node with the event response.
Change the scope of the instruction
By default, the instruction acquires the scope of the controller of its parent node. But that does not apply in all cases. If the scope of the parent controller is exposed to instructions, they are free to modify the properties of the scope. In some cases, your instructions would like to be able to add some properties and methods that are limited to internal use. If we add to the scope of the parent, the parent scope is contaminated. In fact, we still have two options:
A child scope– This scope prototype inherits the child parent scope.
An isolated scope– exists that does not inherit from the scope of the parent scope.
Such a scope can be configured by the directive to define the scope property in the object. The following code fragment is an example:
App.directive (' HelloWorld ', function () {return
{
scope:true,
//Use a child scope that inherits from parent< C4/>restrict: ' AE ',
replace: ' true ',
Template: '
The above code, let angular create a new child scope that inherits from the parent SOCPE to the instruction. Another option, the scope of the isolation:
App.directive (' HelloWorld ', function () {return
{
scope: {},
//] Use a new isolated scope
restrict: ' AE ',
replace: ' true ',
Template: '
This directive uses an isolated scope. The scope of isolation is very beneficial when we want to create reusable instructions. By using the isolation scope, we are able to ensure that our instructions are self-contained and can be easily inserted into HTML applications. It does not have access to the scope of the parent, which ensures that the parent scope is not contaminated. In our HelloWorld instruction example, if we set scope to {}, the above code will not work. It creates a new quarantine scope, the corresponding expression {{color}} will point to this new scope, and its value will be undefined. Using the scope of isolation does not mean that we cannot access the properties of the parent scope at all.
Data binding between the isolation scope and the parent scope
Generally, the scope of the quarantine directive can be a lot of convenience, especially if you are working on multiple scope models. But sometimes in order for the code to work correctly, you also need to access the properties of the parent scope from within the directive. The good news is that angular gives you enough flexibility to have a selective way of passing in the properties of the parent scope by binding. Let's revisit our HelloWorld directive, whose background color changes as the user enters the name of the color in the input box. Remember when we used the quarantine scope for this command and it didn't work? Now, let's get it back to normal.
Let's say we've initialized the angular module that the app's variable is pointing to. So our HelloWorld instructions are as shown in the following code:
App.directive (' HelloWorld ', function () {return
{
scope: {},
restrict: ' AE ',
replace:true,
Template: ' <p style= ' Background-color:{{color}} ' >hello world</p> ',
link:function (scope, Elem, attrs {
elem.bind (' click ', Function () {
elem.css (' background-color ', ' White ');
Scope. $apply (function () {
Scope.color = "white";
});
};
Elem.bind (' MouseOver ', function () {
elem.css (' cursor ', ' pointer ');
});
}
;}
;
The HTML tag that uses this instruction is as follows:
<body ng-controller= "Mainctrl" >
<input type= "text" ng-model= "color" placeholder= "Enter a Color"/>
The code above is now not working. Because we use an isolated scope, the {{color}} expression inside the instruction is quarantined within the scope (not the parent scope) within the instruction. But the ng-model instruction in the outer input box element points to the color attribute in the parent scope. So, we need a way to bind the two parameters in the isolation scope and the parent scope. In angular, this data binding can be implemented by adding attributes to the HTML element in which the instruction is located and by configuring the corresponding scope property in the directive definition object. Let's take a look at several ways to establish data binding.
Select one: use @ To implement one-way text binding
In the following instruction definition, we specify the attribute color in the isolation scope to the parameter colorattr on the HTML element on which the instruction resides. In the HTML tag, you can see that the {{color}} expression is assigned to the COLOR-ATTR parameter. When the value of an expression changes, the color-attr parameter changes as well. The value of the color property in the isolation scope is also changed accordingly.
App.directive (' HelloWorld ', function () {return
{
scope: {
color: ' @colorAttr '
},
....
The rest of the configurations
};
The updated HTML markup code is as follows:
<body ng-controller= "Mainctrl" >
<input type= "text" ng-model= "color" placeholder= "Enter a Color"/>
We call this method a single binding because in this way you can only pass the string (using the expression {}}) to the parameter. When the properties of the parent scope change, the value of the property in your isolation scope model changes. You can even monitor the scope property changes within the command and trigger some tasks. However, the reverse delivery does not work. You cannot change the value of the parent scope by manipulating the isolation scope property.
Note the point:
When isolating the scope attribute and the name of the instruction element parameter, you can set the scope binding in a simpler way:
App.directive (' HelloWorld ', function () {return
{
scope: {
color: ' @ '
},
....
. The rest of the configurations
};
The HTML code for the corresponding instruction is as follows:
Choose two: Use = implement bidirectional binding
Let's change the definition of the directive to the following:
App.directive (' HelloWorld ', function () {return
{
scope: {
color: ' = '
},
...
.. The rest of the configurations
};
The corresponding HTML modifications are as follows:
<body ng-controller= "Mainctrl" >
<input type= "text" ng-model= "color" placeholder= "Enter a Color"/>
Unlike @, this gives you the ability to specify a real scope data model for a property, rather than a simple string. This allows you to pass simple strings, arrays, and even complex objects to the isolation scope. Bidirectional binding is also supported. Whenever the parent scope property changes, the corresponding attribute in the isolation scope changes, and vice versa. As before, you can also monitor changes to this scope property.
Select three: Use & Execute functions in parent scope
Sometimes it is necessary to invoke the functions defined in the parent scope from the isolation scope. In order to be able to access the functions defined in the external scope, we use &. For example, we want to invoke the SayHello () method from within the instruction. The following code tells us what to do:
App.directive (' SayHello ', function () {return
{
scope: {
sayhelloisolated: ' & '
},
....
//The rest of the configurations
};
The corresponding HTML code is as follows:
<body ng-controller= "Mainctrl" >
<input type= "text" ng-model= "color" placeholder= "Enter a Color"/>
<say-hello sayhelloisolated= "SayHello ()"/>
</body>
This plunker example makes a good interpretation of the above concept.
The difference between the parent scope, the child scope, and the isolation scope
As a angular novice, you may find it confusing to choose the right instruction scope. By default, the directive does not create a new scope, but rather inherits the parent scope. But in many cases, this is not what we want. If your command uses the properties of the parent scope heavily, or even creates new attributes, it can contaminate the parent scope. It is not a good idea to have all the instructions using the same parent scope, because anyone can modify the properties in this scope. Therefore, the following principle may help you to choose the right scope for your instructions.
1. Parent scope (SCOPE:FALSE) – this is the default. If your directive does not manipulate the attributes of the parent Scoe, you do not need a new scope. In this case, the parent scope can be used.
2. Sub Scope (Scope:true) – This creates a new scope for the instruction, and the prototype inherits from the parent scope. You should create a new scope if the attributes and methods in the scope of your directive are not related to other directives and to the parent scope. In this way, you also have the properties and methods defined in the parent scope.
3. Isolation scope (scope:{}) – It's like a sandbox! When you create a directive that is self-contained and reusable, you need to use this scope. You will create many scope properties and methods in the instructions that are used only within the instruction and will never be known to the outside world. If so, the scope of isolation is a better choice. The scope of quarantine does not inherit the parent scope.
Transclusion (embedded)
Transclusion is the way to let our instructions contain arbitrary content. We can delay extraction and compile these embedded content under the correct scope, and eventually put them in the location specified in the instruction template. If you set transclude:true in the instruction definition, a new embedded scope is created, and its prototype inherits the child parent scope. If you want your command to use a quarantine scope, but it contains content that can be executed in the parent scope, transclusion can also help.
Let's say we sign up for one of the following directives:
App.directive (' Outputtext ', function () {return
{
transclude:true,
scope: {},
Template: ' <div Ng-transclude></div> '
};
It uses the following:
<div output-text>
<p>hello {{name}}</p>
</div>
Ng-transclude indicates where the embedded content is to be placed. In this example, the DOM content <p>hello {{name}}</p> is extracted and placed inside the <div ng-transclude></div>. One important point to note is that the corresponding property of the expression {{name}} is defined in the parent scope, not the child scope. You can do some experiments in this plunker example. If you want to learn more about scope, you can read this article.
Transclude: The difference between ' element ' and transclude:true
Sometimes I want to embed the instruction element itself, not just its content. In this case, we need to use transclude: ' element '. Unlike Transclude:true, it includes elements marked with ng-transclude instructions in the instruction template. Using transclusion, your link function obtains a link function called transclude, which binds the correct instruction scope and passes in another function that has a copy of the embedded DOM element. You can do this in the transclude function, such as modifying an element copy or adding it to the DOM. Directives like Ng-repeat Use this method to repeat the DOM element. Take a closer look at the Plunker, which uses this method to copy the DOM elements and change the background color of the second instance.
It is also necessary to note that when using transclude: ' element ', the elements of the instruction are converted to HTML annotations. So if you combine transclude: ' element ' and replace:false, then the instruction template is essentially added to the innerhtml of the annotation--that is, nothing happened! Conversely, if you choose to use replace:true, the instruction template replaces the HTML annotation, and everything will work if you wish. Using Replade:false and Transclue: ' element ' is sometimes useful, such as when you need to repeat a DOM element but do not want to keep the first element instance (it will be converted to annotations). To this and puzzled students can read the StackOverflow on the discussion, the introduction of the more clear.
Controller functions and require
If you want to allow other instructions to interact with your instructions, you need to use the controller function. For example, in some cases, you need to combine two instructions to implement a UI component. Then you can add a controller function to the instruction in the following way.
App.directive (' outerdirective ', function () {return
{
scope: {},
restrict: ' AE ',
Controller: function ($scope, $compile, $http) {
//$scope is the appropriate scope for the directive
This.addchild = function ( nesteddirective) {
//This refers to the controller
Console.log (' Got of the message from nested directive: ' + Nestedd Irective.message);};};
This code adds a controller named Outerdirective to the instruction. When another instruction wants to interact, it needs to declare its reference to the controller instance of your instruction (require). This can be done in the following ways:
App.directive (' innerdirective ', function () {return
{
scope: {},
restrict: ' AE ',
require: ' ^ Outerdirective ',
link:function (scope, Elem, Attrs, controllerinstance) {//the Fourth argument is the
Controller instance you require
Scope.message = "Hi, Parent directive";
Controllerinstance.addchild (scope);};
The corresponding HTML code is as follows:
<outer-directive>
<inner-directive></inner-directive>
</outer-directive>
Require: ' ^outerdirective ' tells angular to search for controller in the element and its parent element. The controller instance that is found will be passed into the link function as the fourth parameter. In our example, we send the scope of the embedded instruction to the father instruction. If you want to try this code, open the Plunker in the browser console. At the same time, the last part of this angular official document gives a very good example of instruction interaction and is well worth reading.
A notepad application
In this section, we use the angular instruction to create a simple Notepad application. We'll use HTML5 's localstorage to store our notes. The final product here, you can sneak peek.
We'll create an instruction that shows the Notepad. The user can view the note records that he or she has created. When he clicks on the Add New button, Notepad enters the editable state and allows new notes to be created. When you click the Back button, the new notes are automatically saved. The notes are saved using a factory class called Notefactory, which uses the localstorage. The code in the factory class is very straightforward and understandable. So we'll focus on the code for the instruction.
First step
We start with the Register Notepad instruction.
App.directive (' Notepad ', function (notesfactory) {return
{
restrict: ' AE ',
scope: {},
link:function (Scope, Elem, attrs) {
},
templateurl: ' templateurl.html '
};
Here are a few things to note:
Because we want the instructions to be reusable, we choose to use the quarantine scope. This instruction can have many properties and methods that are not associated with the outside world.
This instruction can be used in the form of attributes or elements, which is defined in the Restrict attribute.
Now the link function is empty.
This command gets the instruction template from the templateurl.html
Second Step
The following HTML forms the template for the instruction.
<div class= "Note-area" ng-show= "!editmode" >
<ul>
<li ng-repeat= "Note in Notes|orderby: ' ID '" >
<a href= "#" ng-click= "Openeditor (note.id)" >{{note.title}}</a>
</li>
</ul >
</div>
<div id= "editor" ng-show= "EditMode" class= "Note-area" true "contenteditable=" NoteText "></div>
<span><a href=" # "ng-click=" Save () "ng-show=" EditMode ">Back</a> </span>
<span><a href= "#" ng-click= "Openeditor ()" ng-show= "!editmode" >add note</a>< /SPAN>
Several important points to note:
The Title,id and content are encapsulated in the note object.
Ng-repeat is used to traverse all notes in notes and to sort in ascending order by automatically generated ID attributes.
We use a property called EditMode to indicate which mode we are in now. In edit mode, the value of this property is true and editable div nodes are displayed. Users enter their own notes here.
If EditMode is false, we are in view mode, showing all notes.
The two buttons are also displayed and hidden based on editMode values.
The Ng-click command is used to respond to a button's Click event. These methods are added to the scope together with EditMode.
The editable Div box is bound to the NoteText and holds the text entered by the user. If you want to edit an existing note, the model initializes the div with its text content.
Third Step
We create a new function called restore () in scope to initialize the various controllers in our application. It will be invoked when the link function executes, and it will be invoked when the Save button is clicked.
Scope.restore = function () {
scope.editmode = false;
Scope.index =-1;
Scope.notetext = ';
};
We create this function inside the link function. EditMode and NoteText have explained this before. Index is used to track the notes currently being edited. When we create a new note, the value of index is set to 1. When we edit an existing note, it contains the ID value of the object.
Fourth Step
Now we're going to create two scope functions to handle edits and save operations.
Scope.openeditor = function (index) {
Scope.editmode = true;
if (index!== undefined) {
Scope.notetext = notesfactory.get (index). Content;
Scope.index = index;
} else {
scope.notetext = undefined;
}
};
Scope.save = function () {
if (scope.notetext!== ') {
var note = {};
Note.title = scope.noteText.length > 10? Scope.noteText.substring (0) + ' ... ': scope.notetext;
Note.content = Scope.notetext;
Note.id = Scope.index!=-1? Scope.index:localStorage.length;
Scope.notes = Notesfactory.put (note);
}
Scope.restore ();
};
There are a few things to note about these two functions:
Openeditor prepare for the editor. If we are editing a note, it gets the contents of the current note and updates the content to the editable div by using Ng-bind.
If we are creating a new note, we will set the notetext to undefined so that when we save the notes, the corresponding listener is triggered.
If the index argument is undefined, it indicates that the user is creating a new note.
The Save function stores notes by using Notesfactory. After the save is complete, it refreshes the notes array so that the listener can monitor changes to the list of notes to be updated in a timely manner.
The Save function call calls Restore () after resetting the controllers, allowing you to enter view mode from edit mode.
Fifth Step
When the link function executes, we initialize the notes array and bind a KeyDown event for the editable Div box to keep our Nodetext model synchronized with the content in the Div. We use this notetext to save our notes.
var editor = Elem.find (' #editor ');
Scope.restore ();
Initialize our app controls
scope.notes = Notesfactory.getall ();
Load notes
editor.bind (' KeyUp keydown ', function () {
scope.notetext = Editor.text (). Trim ();
});
Sixth step
Finally, we use our instructions in HTML just as you would with other HTML elements, and then start taking notes.
Summarize
One important point to note is that anything you can do with jquery, we can do it with angular instructions and use less code. So before using jquery, consider whether we can do the task in a better way without DOM manipulation. Try using angular to minimize the use of jquery.
To take a look at our notebook apps, the ability to delete notes was deliberately omitted. Readers are encouraged to experiment and implement this function themselves. You can go up and down from GitHub to this demo source code.