Angular自定义指令

在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://upload-images.jianshu.io/upload_images/3120119-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按照上面的规则没有查找到需要的控制器,那么就会抛出异常;如果有?前缀时,则就算没查找到,也不会抛出异常。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容