Angular JS - Custom Instructions

Keywords: angular Javascript Attribute

Starting from the definition of instructions, this article records the factors that affect the behavior of instructions when defining an instruction.
Try to feel these factors and write Angular JS applications more efficiently.

 

Directive

Start by defining a simple instruction. Defining an instruction essentially adds functionality in HTML through elements, attributes, classes, or annotations.
The built-in instructions of Angular JS all start with ng. If you want to customize the instructions, it is recommended to customize a prefix to represent your own namespace.
Here we first use my as the prefix:

var myApp = angular.module('myApp', [])
    .directive('myDirective', function() {
    return {
        restrict: 'A',
        replace: true,
        template: '<p>Kavlez</p>'
    };
})


In this way, we can use it this way, noting that the name is camel-case:

<my-directive />
<!-- <my-directive><p>Kavlez</p></my-directive> -->


directive() accepts two parameters

  • Name: string, the name of the instruction
  • factory_function: Function, the behavior of instructions

When an application is started, name is used as the identity of the application to register the object returned by factory_function.

In factory_function, we can set some options to change the behavior of instructions.

 

Here's a record of the options used to define instructions

 

restrict (string)

This property is used to define the form in which instructions are used. This is an optional parameter. The instructions defined at the beginning of this article also use A. In fact, this option defaults to A.  
They are elements (E), attributes (A), classes (C), annotations (M).
(ps:EMAC? EMACS? It's easy to remember.
For example, myDirective defined above can be invoked in any form.

  • E (Elements)

    <my-directive></my-directive>
    
  • A (attribute, default value)

    <div my-directive="expression"></div>
    
  • C (class name)

    <div class="my-directive:expression;"></div>
    
  • M (Notes)

    <--directive:my-directive expression-->

 

priority (Number)

That is, priority, which defaults to 0.
When multiple instructions are declared on the same element, the priority determines which is called first.  
If the priority is the same, it is called in declarative order.
In addition, no-repeat is the highest priority of all built-in instructions.

 

terminal (Boolean)

Terminal? And Boolean?
terminal means whether to stop instructions with lower priority on the current element than the instruction. But the same priority will still be executed.
For example, we add another instruction based on my-directive:

.directive('momDirective',function($rootScope){
    return{
        priority:3,
        terminal:true
    };
})

The call discovers that my-directive will not take effect:

<div mom-directive my-directive="content" ></div>

 

template (String/Function)

At least output something? But template is also optional.
When String type, template can be an HTML segment.
For Function type, template is a function that accepts two parameters, namely:

  • tElement
  • tAttrs

The function returns a string as a template.

 

templateUrl (String/Function)

This is very similar to the template above, but this time a template is requested through the URL. When String is typed, the template URL is naturally a URL. Function type returns a string as a template URL.

 

replace (Boolean/String)

The default value is false. Take the instructions defined at the beginning of the article as an example. Suppose we call the instructions in this way.

<my-directive></my-directive>  

When replace is true, output:

<p>Kavlez</p>

When replace is false, output:

<my-directive><p>Kavlez</p></my-directive>      

 

transclude (Boolean)

This option defaults to false, translated as'embedding', and still feels somewhat astringent.
template and scope can already do a lot of things, but there is one shortcoming.
For example, adding content on the basis of the original elements, transclude's example is as follows:

<body ng-app="myApp">
    <textarea ng-model="content"></textarea>
    <div my-directive title="Kavlez">
        <hr>
        {{content}}
    </div>
</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.directive('myDirective', function() {
    return {
        restrict: 'EA',
        scope: {
            title: '@',
            content: '='
        },
        transclude: true,
        template: '<h2 class="header">{{ title }}</h2>\
        <span class="content" ng-transclude></span>'
    };
});
</script>  

It is found that hr under div has not been removed, and that is the effect.
Be careful not to forget to declare ng-transclude in the template.

 

scope (Boolean/Object)

The default is false, and true inherits from the parent scope and creates its own scope.
The role of ng-controller is also to inherit from the parent scope and create a new scope.
For example, if you leave your own scope, you will be beaten back to the original form:

<div ng-init="content='from root'">
    {{content}}
    <div ng-controller="AncestorController">
        {{content}}     
        <div ng-controller="ChildController">
            {{content}}     
        </div>
        {{content}} 
    </div>
    {{content}} 
</div>

.controller('ChildController', function($scope) {
    $scope.content = 'from child';
})
.controller('AncestorController', function($scope) {
    $scope.content = 'from ancestor';
})


But don't get me wrong, instruction nesting doesn't necessarily change its scope.
Since true inherits from the parent scope and creates its own scope, let's try to see what it would look like to change to false:

<div ng-init="myProperty='test'">
    {{ myProperty }}
    <div my-directive ng-init="myProperty = 'by my-directive'">
        {{ myProperty }}
    </div>
    {{ myProperty }}
</div>

.directive('myDirective', function($rootScope) {
    return {
        scope:false
    };
})

Obviously, the result is three lines'by my-directive'.
Non-true is false? naive!
In fact, the most troublesome is the isolation scope.


Let's change myDirective slightly to output < p >{content} </p>.
So I tried to define it as follows:

<body ng-app="myApp" >
    <p ng-controller="myController">
    <div my-directive="I have to leave." ></div>
        {{myDirective}}
    </p>
</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.directive('myDirective', function($rootScope) {
    $rootScope.myDirective = 'from rootScope';
    return {
        priority:1000,
        restrict: 'A',
        replace: true,
        scope: {
            myDirective: '@',
        },
        template: '<p>{{myDirective}}</p>'
    };
})
.controller('myController',function($scope){
    $scope.myDirective = 'from controller';
});
</script>



It's not @ that needs to be noticed here, but the focus is on the isolation scope.
According to the example output above, the {{myDirective}} in template does not affect other scopes.
Let's try again.

<input type="text" ng-model="content">
<p ng-controller="myController" >
<div my-directive="{{content}}" ></div>
    {{content}}
</p>  

It is found that everyone changes together, that is to say, the value is passed to the isolation scope by copying DOM attributes.
ng-model is a powerful instruction that links its isolation scope to the DOM scope, which is a two-way data binding.


How to transfer data to the isolation scope of instructions? Here we use @.
Or it can be written as @myDirective, that is to say, it can be changed by name or something. For example, I can assign myDirective with @myCafe or something. In a word, it is bound to DOM attributes.

In addition, we can use = to bind bidirectionally to bind the attributes of the local scope to those of the parent scope.
For example, in the following example, the content in the isolation scope can only be'abc':

<body ng-app="myApp" ng-init="content='abc'">
    <p ng-controller="myController" >
        <input type="text" ng-model="content">
        <div my-directive="content" ></div>
        {{content}}
    </p>
</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.directive('myDirective', function($rootScope) {
    return {
        priority:1000,
        restrict: 'A',
        replace: true,
        scope: {
            myDirective: '=',
        },
        template: '<p>from myDirective:{{myDirective}}</p>'
    };
})  
.controller('myController',function($scope){
    $scope.content = 'from controller';
});
</script>

 

Another way to isolate scopes outside of instructions is by &.
We can use & to bind functions with parent scopes, such as the following ex amp le:

<body ng-app="myApp">
    <div ng-controller="myController">
        <table border='1'>
            <tr>
                <td>From</td>
                <td><input type="text" ng-model="from"/></td>
            </tr>
            <tr>
                <td>To</td>
                <td><input type="text" ng-model="to"/></td>
            </tr>
            <tr>
                <td>Content</td>
                <td><textarea cols="30" rows="10" ng-model="content"></textarea></td>
            </tr>
            <tr>
                <td>Preview:</td>
                <td><div scope-example to="to" on-send="sendMail(content)" from="from" /></td>
            </tr>
        </table>
    </div>
</div>

</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.controller('myController',function($scope){
    $scope.sendMail=function(content){
        console.log('content is:::'+content);
    }
})
.directive('scopeExample',function(){
    return{
        restrict:'EA',
        scope: {
            to: '=', 
            from: '=' ,
            send: '&onSend'
        },
        template:'<div>From:{{from}}<br>\
        To:{{to}}<br>\
        <button ng-click="send()">Send</button>\
        </div>'
    }
})
</script>

 

controller (String/Function)

Controllers can also be defined in instructions, such as:

.directive('myDirective', function() {
    restrict: 'A',
    controller: 'myController'
}).controller('myController', function($scope, $element, $attrs,$transclude) {
    //...
})

The same effect can also be stated as follows:

directive('myDirective', function() {
    restrict: 'A',
    controller:function($scope, $element, $attrs, $transclude) {
        //...
    }
});

 

controllerAs (String)

As you can see from the name and type, this option is used to set the alias of the controller.  
For example:

directive('myDirective', function() {
    return {
        restrict: 'A',
        template: '<p>{{ myController.name }}</p>',
        controllerAs: 'myController',
        controller: function() {
            this.name = "Kavlez"
        }
    };
});

 

compile (Object/Function)

It's not very common, but it's worth knowing.
Compoile and link, these two options relate to the life cycle of Angular JS.

First of all, here is a brief record of my understanding of life cycle.

  • Before the application starts, all instructions exist in the form of text.
  • After the application is started, compile and link are started, DOM is changed, scope is bound to HTML.
  • During the compilation phase, Angular JS traverses the entire HTML and processes declared instructions.
  • Another instruction may be used in the template of one instruction. The template of this instruction may contain other instructions, so layer by layer it is a template tree.
  • When DOM is not yet data-bound, the operating cost of DOM is relatively small, and instructions such as ng-repeat are more appropriate to operate on DOM.
  • We can use the compiler function to access the compiled DOM, and use the compiler function to convert the template DOM before data binding. The compiler function will return the template function.
    That is to say, the meaning of setting the compile function is to modify the DOM before the instructions and real-time data are put into the DOM. At this point, DOM can be operated without any worries.
  • Then we can go to the next stage, the link stage.
  • Finally, the template function is passed to the link function specified by the instruction, which links the scope and DOM.


Okay, let's try compile:

<body ng-app="myApp">
    <my-directive ng-model="myName"></my-directive>
</body>
<script type="text/javascript">
var myApp = angular.module('myApp', [])
.directive('myDirective', function($rootScope) {
    $rootScope.myName = 'Kavlez';
    return {
        restrict: 'EA',
        compile:function(tEle, tAttrs, transcludeFn) {
            var h2 = angular.element('<h2></h2>');
            h2.attr('type', tAttrs.type);
            h2.attr('ng-model', tAttrs.ngModel);
            h2.html("hello {{"+tAttrs.ngModel+"}}");
            tEle.replaceWith(h2);
        }
    };
});
</script>

Posted by tzzoug on Wed, 19 Jun 2019 13:08:11 -0700