How AngularJS creates custom commands

Source: Internet
Author: User
Tags angular scope
This article describes how AngularJS creates custom commands. We will share this with you for your reference. The details are as follows:

This is a translation of instructions from angular developers. It is mainly intended for developers who are familiar with angular development basics. This document explains when to create your own commands and how to create commands.

What is an instruction?

At a high level, commands are descriptions of the angular $ compile service. When a specific tag (attribute, element name, or annotation) appears in the DOM, it allows the compiler to append the specified behavior to the DOM.

This process is very simple. Angular has such built-in commands, such as ngBind and ngView. Just like creating controllers and services, you can also create your own commands. When angular is started, Angular compiler analyzes html to match the instruction, which allows the instruction to register or change the DOM.

Matching command

Before writing a command, we first need to know how angular matches a command. In the following example, the input element matches the ngModel command.

 

The following method also matches ngModel:

 

Angular standardizes the label name and attribute name of an element to determine which element matches which command. In js, We reference commands (such as ngModel) by using the canonicalized camper name ). In HTML, the attribute names defined by '-' are often used to call commands (for example, ng-model ).

Standardized processing process:

-Remove the prefix of x-and data-on the element or attribute.
-The format of Conversion ':', '-' and '_-' is camper spelling.

The following example shows how to match the ngBind command in different ways.

 




Best Practice: give priority to names in the '-' format (for example, ng-bind matches ngBind ). If you want to use the HTML verification tool, you can use the 'data-'prefix (such as data-ng-bind ). Names in other formats exist because of historical reasons. Do not use them.

$ Compile service can match commands Based on element names, attribute names, class names, and annotations.

All commands provided by Angular match the attribute name, Tag Name, comment, or class name. The following methods can be resolved

 
 

Best Practice: give priority to instructions by Tag Name and attribute name. This makes it easier to understand which element the specified Element matches.

Best Practice: The annotation method is usually used in DOM APIs to restrict the creation of commands that span multiple elements, such as table elements and restrict repeated nesting. In this case, annotations are required. In AngularJS 1.2, ng-repeat-start and ng-repeat-end are used as a better solution to solve this problem. This method is recommended when possible.

Binding text and attributes

During compilation, the compiler uses the $ interpolate service to detect whether the matching text and attribute values contain nested expressions. These expressions are registered as watches and can be updated during the digest loop.

Hello {{username}}!

Binding ngAttr attributes

Sometimes, the browser is very picky about the attribute values that it deems legal (that is, the attributes of some elements cannot be assigned any value; otherwise, an error is reported ).

For example:

  
  
 

When using this method, we will find an Error in the console: Invalid value for attribute cx = "{cx }}".. this is because of the limitations of the svg dom api. You cannot simply write it as cx = "{cx }}".

Ng-attr-cx can solve this problem.

If a bound property uses the ngAttr prefix (or ng-attr), it will be applied to the corresponding unprefixed attribute during binding, this method allows you to bind to the attribute that needs to be processed by the browser immediately (such as the circle [cx] attribute of the SVG element ).

Therefore, we can fix the above problems as follows:

  
  
 

CREATE Command

First, let's talk about the API for registering commands. Like controller, commands are registered on the module. The difference is that commands are registered through the module. directive API. Module. directive accepts a canonicalized name and a factory function. This factory function returns an object containing different configurations. This object is used to tell the $ compile service how to proceed with the next step.

The factory function is called only once when the compiler matches the instruction for the first time. Initialization is usually performed in factory functions. This function is called using $ injector. invoke, so it can perform dependency injection like controller.

Best Practice: returns a Defined Object first, instead of a function.

Next, we will first understand some common examples, and then gain an in-depth understanding of the principles and compilation processes of different configuration items.

Best Practice: to avoid conflicts with some future standard naming conventions, it is Best to prefix your own commands, such as creating Command, which may cause conflicts. Add HTML7 to introduce the same element. We recommend that you use the prefix of two or three words (such as btfCarousel). You cannot use ng or other prefixes that may conflict with future angular versions.

In the following example, we use the my prefix.

Template extension commands

When you have a large number of templates that represent customer information. This template has been repeated many times in your code. When you change a place, you have to change it elsewhere. At this time, you need to use instructions to simplify your template.

Create a command to replace its content with a static template.

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 made some bindings here, and the $ compile compilation Link

Then, it will match the sub-element instruction. This means you can combine some commands. The following example shows how to do this.

In this example, we write the template directly in the template configuration item, but this is not elegant as the template size increases.

Best Practice: unless your template is very small, it is better to split it into a separate hmtl file and load it using the templateUrl option.

If you are familiar with ngInclude, templateUrl is very similar to it. Now we use templateUrl to rewrite the above example:

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 command to match the Tag Name ? If we simply put If the elements are placed on the hmtl, no effect is found.

Note: When creating commands, only the attribute method is used by default. To create a command that can be triggered by the element name, you need to use the restrict configuration.

Restrict configuration can be set as follows:
-'A' only matches the attribute name.
-'E' only matches the element name.
-'AE' can match the attribute name or element name.

Therefore, we can use restrict: 'e' to configure our commands.

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 will the attribute name or element name be used? When creating a component with its own template, you need to use the element name. If you only want to add a function for an existing element, use the attribute name.

Using the element name as the myCustomer command is a very correct decision, because you do not use some 'customer' behavior to embellish the element, but define an element with its own behavior as the customer component.

Scope of isolation commands

The above myCustomer command is already very good, but it has a fatal defect. We can only use it once in a given scope.

Its current implementation is that every time we reuse this command, we need to create a new controller for it.

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 isolate the directive scope from the external scope, and then map the external scope to the internal scope of the directive. You can create an isolate scope for this purpose. In this case, we use the scope configuration of the command.

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 Bind the customer in the internal scope to naomi. We have defined this naomi in the controller. The second is to bind the customer to igor.

Now let's see how scope is configured.

//...scope: { customer: '=customer'},//...

The property name (customer) is the variable name of the isolated scope on the myCustomer command. Its value (= customer) tells the compiler to bind to the customer attribute.

Note: In the instruction scope configuration, the '= attr' attribute name is the name after standardization, for example, to bind

To bindToThis.

The attribute name is the same as the name of the value you want to bind. You can use the following shortcut Syntax:

//...scope: { // same as '=customer' customer: '='},//...

Another use of isolated scope is to bind different data to the internal scope of the instruction.

In our example, we can add another property vojta to our scope and access it in our instruction template.

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}}Name: {{vojta.name}} Address: {{vojta.address}}

Note that {vojta. name }}and {vojta. address} is empty, meaning they are undefined. Although vojta is defined in the controller, it cannot be accessed inside the command.

As its name implies, the isolate scope of the Directive isolates everything except the data model you added to the scope: {} object. This is very useful for creating a reusable component, because it prevents other things except the data model you want to pass in from changing the state of your data model.

Note: Normally, the scope is the prototype inherited from the parent scope. However, isolate scope does not have such inheritance.

Best Practice: when you want to make your components reusable within the scope of the application, use the scope configuration to create an isolate scopes

Create a DOM Operation Command

In this example, we will create a command to display the current time, update the DOM once per second to correctly display the current time.

Command DOM modification is usually in link configuration. The link option accepts a function link (scope, element, attrs) {...} with the following tags:

-Scope is an angular scope object.
-Elements encapsulated by jqLite Matching element commands (jquery-like libraries implemented inside angular)
-Attrs is an object with the normalized attribute name and corresponding value.

In our link function, we update the display time once per second, or when the user changes the specified binding time format string. We also need to remove the timer to avoid Memory leakage when the command is deleted.

Date format:

Current time is:

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 many things worth noting here, just like the module. controller API, function parameters in module. directive are dependent injection. Therefore, we can use the $ timeout and dataFilter services inside the Link function.

We registered an event element. on ('$ destroy',...). What triggered this $ destory event?

AngularJS triggers some specific events. When a DOM element compiled by angular is removed, it triggers a $ destory event. Similarly, when an angular scope is removed, it broadcasts the $ destory event to all listener scopes.

By listening to events, you can remove event listeners that may cause memory leakage. Listeners registered on elements and scopes are automatically cleared when they are removed, however, if you register an event on a service or a DOM node that has not been deleted, you must manually clean it up. Otherwise, there is a risk of Memory leakage.

Best Practice: some cleanup operations should be performed when the removal is performed. You can use element. on ('$ destroy ',...) or scope. on ('$ destroy ',...) to run the unbind function,

Instructions for creating other elements of a package

Now we have implemented the following: Use isolate scopes to pass the data model to the instruction. However, sometimes we need to be able to pass an entire template instead of a string or object. Let's explain by creating the 'Dialog box' component. This 'Dialog box' component should be able to wrap any content.

To achieve this, we use transclude Configuration

Check out the contents, {{name}}!

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

What is the transclude configuration used? Transclude enables the content of the instruction with this configuration to access the external scope of the instruction.

Referring to the following example, we have added a link function. In this link function, we have redefined the name attribute value as Jeff, so what value will the {name} be parsed into now?

Check out the contents, {{name}}!

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

Generally, we will think that {name} Will be parsed as Jeff. However, here, we can see that the {name} in this example is still parsed as Tobias.

The transclude configuration changes the way commands are nested with each other. It gives the command content any external scope of the command, rather than internal scope. To achieve this, it gives the instruction content an opportunity to access the external scope.

Such behavior is very meaningful for the instructions on the package content. If this is not the case, you must input each data model you need. If you need to input each data model to be used, you cannot adapt to different situations.

Best Practice: Use transclude: true only when you want to create an instruction to enclose any content.

Next, add a button to the 'Dialog box' component to allow users to bind their own behaviors using commands.

Check out the contents, {{name}}!

JS

angular.module('docsIsoFnBindExample', []) .controller('Ctrl', function($scope, $timeout) { $scope.name = 'Tobias'; $scope.hideDialog = function () {  $scope.dialogIsHidden = true;  $timeout(function () {  $scope.dialogIsHidden = false;  }, 2000); }; }) .directive('myDialog', function() { return {  restrict: 'E',  transclude: true,  scope: {  'close': '&onClose'  },  templateUrl: 'my-dialog-close.html' }; });

My-dialog-close.html

×

We want to call the function in the scope of the instruction to run the function we passed in, but this function is the context of running at the definition (js is usually like this ).

Previously we saw how to use '= prop' for scope configuration, but in the above example, we used' & prop', '&' to bind a function to isolated scope, allow isolated scope to call it and maintain the original function scope (here the scope refers to $ scope ). Therefore, when a user clicks x, the close function of the Ctrl controller is run.

Best Practice: when your command wants to open an API to bind a specific behavior, use '& prop' in scope configuration '.

Create a command to add an event listener

Previously, we used the link function to create a command to operate the DOM element. Based on the above example, we created a command to add event listening to the element.

For example, if we want to create a drag-and-drop element, what should we do?

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 commands for mutual communication

You can use commands in the template to combine any commands.

Sometimes, you want to create a command from other commands

Imagine you want a container with a tab. The Container content corresponds to the active tab.

   
  
     Hello  
   

Lorem ipsum dolor sit amet

World Mauris elementum elementum enim at suscipit.

counter: {{i || 0}}

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

  • {{pane.title}}

My-pane.html

The myPane command has a require: '^ myTabs' configuration. When the command uses this configuration, the $ compile service is called the myTabs command and obtains its controller instance. If not, an error is thrown. The prefix of __^ _ ^ 'indicates that the command searches for the Controller on its parent element (without the prefix of __^ _ ^', the command searches for the specified command on its own element by default ).

Where does the controller of myTabs come from? You can use the controller configuration to specify a controller for the command. In the above example, myTab uses this configuration. Like ngController, this configuration binds a controller to the template of the command.

Let's look at our definition of myPane's, and notice the last parameter of the link function: tabCtrl. When one command contains another command (in the require mode ), it will receive the Controller instance of this command as the fourth parameter of the link function. With this, myPane can call the addPane function of myTabs.

Savvy readers may want to know the difference between link and controller. The most basic difference is that the controller opens an API (that is, this controller instance can be read by other instances ), the link function can interact with the Controller through require.

Best Practice: Use the controller when you want to open an API to other commands; otherwise, use the link function.

Summary

Here we will explain the main use cases of some commands. Each one can serve as a good starting point for creating your own commands.

If you want to learn more about the compilation process, you can view the compiler guide content.

The $ compile API page provides detailed descriptions of each direve ve configuration item. For more information, see.

I hope this article will help you with AngularJS programming.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.