Angular自定义指令

作者: liuchungui | 来源:发表于2018-04-14 20:12 被阅读0次

    在Angular当中,指令是可以用来实现你想做的任何事情的利器,它可以给Dom绑定你所指定的行为(例如事件监听、添加点击事件),甚至是它都可以直接转换Dom元素(例如直接将<div>标签转换成一个列表)。

    而在原生的Angular中自带了很多的指令,例如ngModel、ngClass、ngRepeat等等。但有时官方的指令满足不了我们的需求,这时就需要创建特定的指令,例如当前项目中的手机号限制指令、富文本字数限制指令、模糊搜索指令等等。而且,指令可以减少重复逻辑代码的编写,方便使用,提升开发效率。所以,学习下如何自定义指令是很有必要的。

    那么我们如何自定义指令?

    指令的一般模板

    var myModule = angular.module([]);myModule.directive('myDirective', function() {    return {        restrict: 'EA',        priority: 1000,        template: '<div></div>',        templateUrl: 'test.html',        replace: true,        transclude: true,        scope: true,        controller: function ($scope, $element, $attrs, $transclude) {                    },        require: 'ngRepeat',        link: function (scope, iElement, iAttrs) {        }    };});
    

    它里面有很多属性,每个属性的作用如下图:

    image.png

    实现一个指令

    第一个指令,helloWorld

    实现指令的代码如下:

    demoApp.directive('helloWorld', function () {
        return {
            restrict: 'E',
            template: '<h4>Hello world</h4>'
        }
    });
    

    当实现之后,我们就可以在我们的html中使用helloWorld指令了,如下:

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

    在上面的helloWorld指令中,我们用到下面一些内容:

    指令命名

    在指令的定义中使用的是驼峰式命名,在模板使用时,使用
    的是通过短横线连接的。主要是能够支持HTML校验规则,我
    们这里使用的是H5的HTML规则。

    例如,上面的helloWorld指令,我们在定义指令的时候,使用的是hellWorld这种驼峰式,而我们在模板中使用指令的时候是<hello-world></hello-world>,使用的是用短横线连接。

    restrict属性

    它描述指令的声明风格,即它是否可以作元素名称元素属性样式类或者注释

    image.png

    注意:当想让一个指令可以作为元素,也可以作为属性时,可以使用“AE”

    例如,上面的helloWolrd指令,只是作为一个元素,因为它的restrict为E

    模板

    模板有两种形式:

    • template:以字符串形式编写的一个内联模板
    • templateUrl:加载模板的URL

    上面的helloWorld指令,使用的是template属性,直接填充<h4>Hello world</h4>字符串作为指令的内容。

    最终,我们的helloWorld使用的,达到的效果如下:


    image.png

    除了,上面helloWorld使用的restrict和template外,还有一些重要的属性,例如replace、transclude、scope、link,我们都来学习下。

    replace

    为true,则替换指令所在的元素;为false,则把当前指令追加到指令所在的元素内部,默认false。

    若是,将helloWorld指令replace设置为true,代码如下:

    demoApp.directive('helloWorld', function () {    return {        restrict: 'E',        template: '<h4>Hello world</h4>',        replace: true    }});
    

    指令转换结果:


    image.png

    transclude

    为true,则把指令所在元素中原来的子节点移动到ng-transclude所在元素内;为false,则直接忽略内部的子节点,默认false。

    若是将helloWorld指令transclude设置为true,它的代码如下:

    demoApp.directive('helloWorld', function () {    return {        restrict: 'E',        template: '<h4>Hello world<span ng-transclude style="color: red"></span></h4>',        replace: true,        transclude: true    }});
    

    指令转换结果:

    image.png

    scope属性

    指令的作用域,默认false。当为false,为所在Dom元素上的作用域;当设置true时,创建一个新的作用域,所在元素的作用域是它的父作用域,它继承父作用域上所有的属性;当设置scope为一个对象时,创建一个独立的作用域,所在元素的作用域仍然是它的parent,但它不继承父对象上任何属性。

    当scope设置false时

    html代码:

    <body ng-controller="demoController">
        <div>指令外: {{name}}</div>
        <hello-world>测试</hello-world>
    </body>
    

    js代码:

    var demoApp = angular.module('demoApp', []);
    demoApp.controller('demoController', function ($scope) {
        $scope.name = "Jack";
    });
    
    demoApp.directive('helloWorld', function () {
        return {
            restrict: 'E',
            template: '<h4>指令内部: {{name}}</h4>',
            replace: true,
            transclude: true,
            scope: false,
            link: function (scope) {
                scope.name = "test";
            }
        }
    });
    

    效果如下:


    image.png

    解析:因为指令外和指令内部的是同一个作用域,所以指令内部的name改变,也造成指令外面变化。

    当scope设置true时

    html代码:

    <body ng-controller="demoController">
        <div>指令外: {{name}}</div>
        <hello-world>测试</hello-world>
    </body>
    

    js中,指令的实现代码如下:

    var demoApp = angular.module('demoApp', []);demoApp.controller('demoController', function ($scope) {    $scope.name = "Jack";});demoApp.directive('helloWorld', function () {    return {        restrict: 'E',        template: '<h4>指令内部, 改变之前:{{preName}}, 改变之后: {{name}}</h4>',        replace: true,        transclude: true,        scope: true,        link: function (scope) {            scope.preName = scope.name;            scope.name = "test";        }    }});
    

    结果:


    image.png

    解析:指令内部改变之前的名字是Jack,与外面一样;改变之后,指令外部没有变化,仍然是test。可以分析出,当设置为true时,指令内部继承了外部的属性,但它是一个新的作用域。还可以通过console.log打印进一步验证,外面的作用域是内部作用域的parent。

    scope设置一个对象

    image.png

    使用时,在html中的代码如下:

    <body ng-controller="demoController">    <div>指令外: {{name}}</div>    <hello-world new-name="name">测试</hello-world></body>
    

    js中,指令的实现代码如下:

    var demoApp = angular.module('demoApp', []);demoApp.controller('demoController', function ($scope) {    $scope.name = "Jack";});demoApp.directive('helloWorld', function () {    return {        restrict: 'E',        template: '<h4>指令内部, name:{{name}}, scopeName: {{scopeName}}</h4>',        replace: true,        transclude: true,        scope: {scopeName: '=newName'},        link: function (scope) {            scope.name = scope.name;            scope.scopeName = "test";        }    }});
    

    结果:

    ![image.png](https://img.haomeiwen.com/i3120119/687abc62226d3ad4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    

    解析:name内容不存在,说明没有继承父scope的属性;因为使用’=newName’绑定了外部属性’name’,所以内部改变,指令外的值也变化了。

    link函数

    链接函数,在链接阶段执行,可以在内部操作Dom、添加事件监听、设置数据绑定等等。

    例如,实现的一个限制手机号的指令,解决了当input为num类型时不能限制11位数字的问题。内部使用element.on监听Dom事件,使用element.after添加元素。

    html使用代码:

    <input type="text" ng-model="phone" placeholder="请输入手机号" phone-limit/>
    

    js中,指令的实现代码如下:

    .directive('phoneLimit', function () {    return {        restrict: "A",        scope: {            model: '=ngModel'        },        link: function (scope, element, attrs, ctrl) {            // console.log(element)            //添加一个错误提示元素            function appendErrorTip() {                var msg = "<span class='error-tip'></span>";                element.after(msg);                // $(element).after(msg);            }            //输入限制            function inputLimit() {                var value = String(element.val());                //截取到哪一个字符位置                var findIndex = -1;                for(var i = 0; i < value.length; i++) {                    if(value[i] < '0' || value[i] > '9') {                        findIndex = i;                        break;                    }                    if(i >= 11) {                        findIndex = i;                        break;                    }                }                //查找到了, 则截取                if(findIndex != -1) {                    element.val(value.substr(0, findIndex));                    scope.model = element.val();                }            }            //绑定输入事件            element.on('input', inputLimit);            //添加错误提示元素            appendErrorTip();            element.on("blur", function () {                var value = element.val();                //验证不通过,标红                if(value == undefined || value.length != 11) {                    element.next().html('手机号不正确');                }            })        }    }});
    
    

    指令间通信

    用到了两个属性,如下:

    • controller:创建一个控制器,通过这个控制器,可以实现指令之间的通信
    • require:要求必须存在另一个指令,当前指令才能正确运行

    主要是通过控制器进行通信的,通过require属性语法,可以把require中的指令的控制器传递给当前指令,从而实现了通信。

    其中,require的用法:
    如:require: '?^^myTabs'

    ^^前缀:说明angular会从所有父元素上查找到myTabs指令,并且获取myTabs指令的控制器;当是^前缀时,说明angular会从当前元素和所有父元素上查找到myTabs指令,并且获取myTabs指令的控制器;当没有^^^时,则说明angular只会从当前元素查找到myTabs指令,并且获取myTabs指令的控制器。

    ?前缀:可选的意思。具体是,当没有?前缀时,angular按照上面的规则没有查找到需要的控制器,那么就会抛出异常;如果有?前缀时,则就算没查找到,也不会抛出异常。

    相关文章

      网友评论

        本文标题:Angular自定义指令

        本文链接:https://www.haomeiwen.com/subject/ayayhftx.html