Angularjs instruction practice

Time:2021-1-28

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 instructions. This tutorial will show you how to customize instructions and how to use them in real projects. At the end of this article, I’ll show you how to use the angular command to create a simple Notepad application.

summary

An instruction is used to introduce a new HTML syntax. An instruction is a tag on a DOM element that makes the element have a specific behavior. For example, static HTML doesn’t know how to create and present a date selector control. For HTML to recognize this syntax, we need to use instructions. Directive creates an element that supports date selection in some way. We’ll show you step by step how this is achieved. If you’ve ever written an application for angularjs, you must have used the instructions, whether you realize it or not. You must have used simple commands, such as ng mode, ng repeat, ng show, etc. These instructions give DOM elements specific behavior. For example, ng repeat repeats a specific element, and ng show conditionally displays an element. If you want an element to support drag and drop, you also need to create an instruction to implement it. The basic idea behind the instructions is simple. It makes HTML truly interactive by listening to element binding events or changing dom.

JQuery Perspective

Imagine using jQuery to create a date selector. First, we add a normal input box to the HTML, and then transform it into a date selector by calling $(element). Datapicker() in jQuery. But think about it. When a designer comes to check the HTML tag, can he / 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 that. The method of angular is to use a directive to extend HTML. Therefore, the instruction of a date selector can be in the following form:

<input type="text" />

Create custom instructions: this way of creating UI components is more direct and clear. You can easily see what this is by looking at the elements.

An angular instruction can have the following four forms:

  1. A new HTML element (< data picker > < / data picker >)

  2. Attribute of the element (< input type = “text” data picker / >)

  3. CSS class(<input type=”text” class=”data-picker”/>)

  4. Notes(<!–directive:data-picker –>

Of course, we can control the presentation of our instructions in HTML. Let’s take a look at the writing of a typical instruction in angularjs. The way of instruction registration is the same as that of controller, but what it returns is a simple object (instruction definition object) with instruction configuration properties. The following code is a simple exampleHello WorldInstructions.

var app = angular.module('myapp', []);

app.directive('helloWorld', function() {

  return {

      restrict: 'AE',

      replace: 'true',

      template: '<h3>Hello World!!</h3>'

  };

});

In the code above, app.directive The () method registers a new instruction in the module. The first argument to this method is the name of the instruction. The second parameter is a function that returns the object defined by the instruction. If your instructions depend on other objects or services, such as $rootscope, $HTTP, or $compile, they can be injected at this time. This instruction is used as an element in HTML, as follows:

<hello-world/>

//OR

<hello:world/>

Or, use it as an attribute:

<div hello-world></div>

//OR

<div hello:world/>

If you want to conform to the HTML5 specification, you can prefix the element with X – or data -. So the following tag also matches the HelloWorld Directive:

<div data-hello-world></div>

//OR

<div x-hello-world></div>

Restrict – this attribute is used to specify how instructions are used in HTML (remember the four representations of instructions mentioned earlier). In the above example, we use ‘AE’. So this directive can be used as a new HTML element or attribute. If we want to allow the instruction to be used as a class, we set restrict to ‘AEC’.

be careful:When matching instructions, angular removes the X – or data – prefix from the names of elements or attributes. Then the – or: concatenated string is converted to a camel case representation and matched with the registered instruction. That’s why we use the HelloWorld directive in HTML in a hello world way. In fact, this is related to the fact that HTML is case insensitive to tags and attributes. Although the above instructions only realize the display of static text, there are still some interesting points worth exploring. We use three properties to configure the instruction in the instruction definition process. Let’s talk about their roles one by one.

  • Template – this attribute specifies the HTML tag generated after the instruction is compiled and linked by angular. This property value does not have to be a simple string. Templates can be very complex and often contain other instructions, as well as expressions ({}}). More often, you will see templateurl instead of template. So, ideally, you should put the template in a specific HTML file and 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 way of < Hello world > < / Hello world >, and set replace to true. Therefore, after the instruction is compiled, the generated template content replaces < Hello world > < / Hello world >. The final output is < H3 > Hello world! < / H3 >. If you set replace to false, which is the default value, the generated template will be inserted into the element that defines the instruction.

Open thisplunkerIn “Hello world!”, right-click to check the content of the element to better understand these.

Link function and scope

The template generated by the instruction does not make much sense unless it is compiled under a specific scope. By default, the directive does not create a new subscope. More often, 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 link property in the instruction definition object. Let’s change our HelloWorld command. When the user enters the name of a color in an input box, the background color of Hello World text changes automatically. At the same time, 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" />

  <hello-world/>

</body>

The HelloWorld command modified by JavaScript is 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');

      });
    }
  };
});

Scope – the scope of the instruction. In our example, the scope of the instruction is the scope of the parent controller. We notice the link function in the instruction definition. It has three parameters:

  • Element – jqlite (a subset of jQuery) of the instruction wraps DOM elements. If you introduced jQuery before introducing angularjs, this element is the jQuery element, not the jqlite element. Since this element has been wrapped by jQuery / jqlite, we don’t need to use $() to wrap it when we do DOM operations.

  • Attr – a standardized parameter object that contains the attributes of the element in which the instruction is located. For example, if you add some attributes to an HTML element, you can use the attrs.someAttribute To use it.

The link function is mainly used to add event listening for DOM elements, monitor model attribute changes, and update dom. In the instruction code fragment above, we added two events, click, and mouseover. The click handler is used to reset the background color of < p > while the mouseover handler changes the mouse to pointer. There is an expression {{color}} in the template, which is used to change the background color of Hello World text when the color in the parent scope changes. This “pluker” demonstrates these concepts.

Compile function

The compile function is used to do some DOM modification before the link function is executed. It receives the following parameters:

  • Element – the element in which the instruction is located

  • Attrs – a standardized list of parameters assigned to an element

Note that the compile function cannot access the scope and must return a link function. But if you don’t set the compile function, you can configure the link function normally. (with compile, you can’t use link, which is returned by compile). The compile function can be written as follows:

app.directive('test', function() {

  return {

    compile: function(tElem,attrs) {

      //do optional DOM transformation here

      return function(scope,elem,attrs) {

        //linking function here

      };
    }
  };
});

How are instructions compiled? In most cases, you just need to use the link function. This is because most of the instructions only need to consider registering event listening, monitoring model, and updating DOM, which can be done in the link function. However, for instructions such as ng repeat, DOM elements need to be cloned and repeated many times, which is completed by the compile function before the link function is executed. This raises the question: why do we need two separate functions to complete the generation process? Why can’t we just use one? To answer this question, we need to understand how instructions are compiled in angular!

When the application boot starts, angular starts to traverse DOM elements using the $compile service. This service searches for instructions in markup text based on registered instructions. Once all the instructions are recognized, angular executes their compile method. As mentioned earlier, the compile method returns a link function, which is added to the list of link functions to be executed later. This is called the compile phase. If an instruction needs to be cloned many times (for example, ng repeat), the compile function is executed only once in the compilation phase. These templates are copied, but the link function is executed for each copied 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 linking phase begins. At this stage, all the collected link functions are executed one by one. The template created by the instruction will be parsed and processed in the correct scope, and then the real DOM node with event response will be returned.

Change the scope of the instruction

By default, the instruction gets the scope of the controller of its parent node. But that doesn’t apply in all cases. If the scope of the parent controller is exposed to the instruction, they can modify the properties of the scope at will. In some cases, your instructions want to be able to add properties and methods for internal use only. If we add it to the parent’s scope, it will pollute the parent’s scope. In fact, we have two choices:

  • A child scope – this scope prototype inherits the child parent scope.

  • An isolated scope – an isolated scope that does not inherit from the parent scope.

Such a scope can be configured through the scope attribute in the instruction definition object. The following code fragment is an example:

app.directive('helloWorld', function() {

  return {

    scope: true,  // use a child scope that inherits from parent

    restrict: 'AE',

    replace: 'true',

    template: '<h3>Hello World!!</h3>'

  };

});

In the above code, ask angular to create a new child scope inherited from the parent Society for the instruction. Another option, isolated scope:

app.directive('helloWorld', function() {

  return {

    scope: {},  // use a new isolated scope

    restrict: 'AE',

    replace: 'true',

    template: '<h3>Hello World!!</h3>'

  };

});

This directive uses an isolated scope. The isolated scope is very useful when we want to create reusable instructions. By using isolated scope, we can ensure that our instructions are self-contained and can be easily inserted into HTML applications. It cannot access the parent’s scope internally, which ensures that the parent’s scope will not be polluted. In our example of HelloWorld instruction, if we set scope to {}, the above code will not work. It will create a new isolated scope, then the corresponding expression {{color}} will point to the new scope, and its value will be undefined. Using isolated scope does not mean that we cannot access the properties of the parent scope at all.

Isolate data binding between scope and parent scope

Generally, the scope of isolated instructions will bring a lot of convenience, especially when you have to operate 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 selectively pass in the properties of the parent scope by binding. Let’s review our experiencehelloWorldCommand, its background color will change according to the color name entered by the user in the input box. Remember when we used isolated scope for this instruction, it didn’t work? Now, let’s get it back to normal.

Suppose we have initialized the angular module that the app variable points to. So our HelloWorld instruction is as follows:

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 tags that use this command are as follows:

<body ng-controller="MainCtrl">

  <input type="text" ng-model="color" placeholder="Enter a color"/>

  <hello-world/>

</body>

Option 1:Using @ to achieve one-way text binding, the above code does not work now. Because we use an isolated scope, the {{color}} expression inside the instruction is isolated in the scope inside the instruction (not the parent scope). But the ng model instruction in the external input box element points to the color attribute in the parent scope. Therefore, we need a way to bind the two parameters in the isolated scope and the parent scope. In angular, this kind of data binding can be realized by adding attributes to the HTML element where the instruction is located and configuring the corresponding scope attribute in the instruction definition object. Let’s take a closer look at several ways to build data binding.

In the instruction definition below, we specify that the attribute color in the isolation scope is bound to the parameter colorattr on the HTML element of the instruction. In the HTML tag, you can see that the {{color}} expression is assigned to the color attr parameter. When the value of the expression changes, so does the color attr parameter. The value of the color attribute in the isolation scope is changed accordingly.

app.directive('helloWorld', function() {

  return {

    scope: {

      color: '@colorAttr'

    },

    ....

    // the rest of the configurations

  };

});

The updated HTML tag code is as follows:

<body ng-controller="MainCtrl">

  <input type="text" ng-model="color" placeholder="Enter a color"/>

  <hello-world color-attr="{{color}}"/>

</body>

Note: we call this method singleton binding because in this way, you can only pass strings (using the expression {}) to parameters. When the properties of the parent scope change, the values of the properties in your isolated scope model change. You can even monitor the change of the scope attribute inside the command and trigger some tasks. However, reverse passing does not work. You cannot change the value of the parent scope by manipulating the isolate scope property.

When the isolation scope attribute has the same name as 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 of the corresponding instruction is as follows:

<hello-world color="{{color}}"/>

Option 2:Use = to achieve bidirectional binding

Let’s change the definition of instruction to the following:

app.directive('helloWorld', function() {

  return {

    scope: {

      color: '='

    },

    ....

    // the rest of the configurations

  };

});

The corresponding html is modified as follows:

<body ng-controller="MainCtrl">

  <input type="text" ng-model="color" placeholder="Enter a color"/>

  <hello-world color="color"/>

</body>

Option 3:Using & to execute functions in the parent scope is different from @ in that it allows you to specify a real scope data model for the attribute instead of a simple string. In this way, you can pass simple strings, arrays, and even complex objects to the isolated scope. At the same time, it also supports bidirectional binding. Whenever the parent scope property changes, the properties in the corresponding isolated scope also change, and vice versa. As before, you can also monitor the change of the scope property.

Sometimes it is very necessary to call the functions defined in the parent scope from isolated scope. In order to access the functions defined in the external scope, we use &. For example, we want to call the sayhello () method from within the instruction. The following code tells us what to do:

app.directive('sayHello', function() {

  return {

    scope: {

      sayHelloIsolated: '&amp;'

    },

    ....

    // 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 is the difference between parent scope, child scope and isolated scopePlunkerThe above concepts are well explained by examples.

As a novice to angular, you may be confused when choosing the right command scope. By default, the instruction does not create a new scope, but follows the parent scope. But in many cases, that’s not what we want. If your command heavily uses the properties of the parent scope, or even creates new properties, it will contaminate the parent scope. It’s not a good idea to have all instructions use the same parent scope, because anyone can modify the properties in the scope. Therefore, the following principle may help you choose the right scope for your instructions.

1. Parent scope (scope: false) – this is the default. If your directive does not operate on the properties of the parent scope, you do not need a new scope. In this case, the parent scope can be used.

2. Child scope (scope: true) – this creates a new scope for the instruction, and the prototype inherits from the parent scope. If the properties and methods in your instruction scope have nothing to do with other instructions and the parent scope, you should create a new scope. In this way, you also have the properties and methods defined in the parent scope.

3. Isolate scope (scope: {}) – it’s like a sandbox! When you create instructions that are self-contained and reusable, you need to use this scope. You will create many scope properties and methods in the instruction. They are only used inside the instruction and will never be known by the outside world. If so, the isolated scope is a better choice. The isolated scope does not inherit the parent scope.

Transition (embedded)

Inclusion is a way to make our instructions contain anything. We can delay extraction and compile these embedded contents in the correct scope, and finally put them into the specified position in the instruction template. If you set t in the instruction definition ransclude:true A new embedded scope will be created, and its prototype inherits the child parent scope. If you want your instruction to use an isolated scope, but the contents contained in it can be executed in the parent scope, the transition can also help.

Suppose we register an instruction as follows:

app.directive('outputText', function() {

  return {

    transclude: true,

    scope: {},

    template: '<div ng-transclude></div>'

  };

});

It is used as follows:

<div output-text>

  <p>Hello {{name}}</p>

</div>

Include: ‘element’ and t ransclude:true The difference ng – include indicates where to place the embedded content. In this example, DOM content < p > Hello {{name}} < / P > is extracted and placed inside < div ng include > < / div >. It is important to note that the attribute corresponding to the expression {name} is defined in the parent scope, not the child scope. You can do some experiments in this example of plunker. If you want to learn more about scope, you can readThis article

Sometimes we embed the instruction element itself, not just its content. In this case, we need to use include: ‘element’. It’s the same as t ransclude:true Instead, it includes the elements marked with the ng exclude instruction into the instruction template. By using the transition, your link function will get a link function called transition, which binds the correct instruction scope and passes in another function with a copy of the embedded DOM element. You can modify the element copy or add it to the DOM in the include function. Instructions like ng repeat use this to repeat DOM elements. Take a closer look at this plonker, which copies DOM elements in this way and changes the background color of the second instance.

It should also be noted that when using include: ‘element’, the element in which the instruction is located will be converted into HTML comments. So, if you use include: ‘element’ and replace:false Then the instruction template is essentially added to the annotated innerHTML – that is to say, nothing happened! Instead, if you choose to use replace:true , the instruction template replaces the HTML comment, so everything works as you wish. use replade:false And include: ‘element’ are sometimes useful, such as when you need to repeat DOM elements but don’t want to keep the first element instance (which will be converted to an annotation). Students who are still confused about this can read theThis is a discussionThe introduction is quite clear.

Controller function 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 the message from nested directive:' + nestedDirective.message);

      };

    }

  };

});

JavaScript adds a controller called outerdirective to the instruction. When another instruction wants to interact, it needs to declare its reference to your instruction controller instance. It can be realized 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. In this way, the found controller instance 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, please open the plunker with the browser console open. At the same time,This is the official document of angularThe last part on gives a very good example of instruction interaction, which is worth reading.

A notepad application

In this section, we use the angular instruction to create a simple Notepad application. We will use HTML5’s local storage to store notes. Where is the final producthereYou can see it first.

We’ll create an instruction to show Notepad. The user can view the notes he / she has created. When he clicks the add new button, Notepad will go into the editing state and allow to create new notes. When you click the back button, the new notes will be saved automatically. Notes are saved using a factory class called notefactory, which uses localstorage. The code in the factory class is very straightforward and understandable. So let’s focus on the code of the instruction.

The first step

Let’s start by registering the Notepad command.

app.directive('notepad', function(notesFactory) {

  return {

    restrict: 'AE',

    scope: {},

    link: function(scope, elem, attrs) {

    },

    templateUrl: 'templateurl.html'

  };

});

Because we want instructions to be reusable, we choose to use isolated scope. This instruction can have many properties and methods that are not related to the outside world. Here are some points to note:

  • This instruction can be used as an attribute or element, which is defined in the restrict attribute.

  • Now the link function is empty

  • This command starts from templateurl.html Get instruction template from

Step two

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" contenteditable="true" ng-bind="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>

The note object encapsulates the title, ID and content. Several important points to note:

  • Ng repeat is used to traverse all the notes in notes and sort them in ascending order according to the automatically generated ID attribute.

  • We use a property called EditMode to indicate which mode we are in. In edit mode, the value of this property is true, and the editable div node will be displayed. Users enter their notes here.

  • If EditMode is false, we will view the mode and display all notes.

  • The two buttons are also displayed and hidden based on the value of EditMode.

  • The ng click command is used to respond to the click event of a button. These methods will be added to the scope along with EditMode.

  • The editable div box is bound with notetext to store the text input by the user. If you want to edit an existing note, the model will initialize the div box with its text content.

The third step

We create a new function called restore () in scope, which is used to initialize various controllers in our application. It is called when the link function is executed and when the Save button is clicked.

scope.restore = function() {

  scope.editMode = false;

  scope.index = -1;

  scope.noteText = '';

};

Step four

We create this function inside the link function. EditMode and notetext have been explained before. Index is used to track the notes currently being edited. When we create a new note, the value of index will be set to – 1. When we edit an existing note, it contains the ID value of the note object.

Now we need to create two scope functions to handle editing and saving 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, 10) + '. . .' : scope.noteText;

    note.content = scope.noteText;

    note.id = scope.index != -1 ? scope.index : localStorage.length;

    scope.notes = notesFactory.put(note);

  }

  scope.restore();

};

Openeditor prepares the editor. If we are editing a note, it will get the content of the current note and update the content to an editable div by using ng bind. These two functions have several points to note:

  • If we are creating a new note, we will set notetext to undefined to trigger the corresponding listener when we save the note.

  • If the index parameter is undefined, it indicates that the user is creating a new note.

  • The save function stores notes by using notesfactory. After saving, it will refresh the notes array, so that the listener can monitor the change of the notes list and update it in time.

  • Save function calls call restore () after resetting controllers, so that you can enter the viewing mode from edit mode.

Step five

When the link function is executed, we initialize the notes array and bind a Keydown event to the edit div box to ensure that our nodetext model is 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();

});

Step six

Finally, let’s use our instructions in HTML just like other HTML elements, and then start taking notes.

<h1 class="title">The Note Making App</h1>

<notepad/>

Source code download:https://github.com/jsprodotcom/source/blob/master/AngularJS_Note_Taker-source_code.zip

summary

A very important point to note is that anything we can do with jQuery can be done with angular instructions, and with less code. Therefore, before using jQuery, please consider whether we can complete the task in a better way without DOM operation.

Reprinted from:http://www.codeceo.com