Angular directive 实例详解

准备代码,会在实例中用到html

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

angular指令定义大体以下express

app.directive('directiveName', function() {
  return {
    // config
  }
})

其中return返回的配置对象包含不少参数,以下一一说明。数组

1. restrict

值为字符串,可选参数,指明指令在DOM中以什么形式被声明缓存

<!-- E (element) -->
<directiveName></directiveName>

<!-- A (attribute) -->
<div directiveName="expression"></div>

<!-- C (class) -->
<div class="directiveName"></div>

<!-- M(comment) -->
<!--directive:directiveName expression-->

默认值为restrict: 'EA',表示能够在DOM里面能够用元素形式和属性形式被声明。通常来讲,若是你想建立一个本身模板的组件时,则使用元素形式,可是仅仅是为已有元素添加功能的话,就使用属性名。服务器

若是想要支持IE8,则最好使用属性和类形式来定义,另外从angular 1.3.X开始,已经放弃支持IE8了。app

2. priority

数字,可选参数,致命指令的优先级,若在单个DOM元素上有多个指令,则优先级高的先执行。cors

固然,设置指令的优先级不太经常使用,可是比较特殊的例子是,内置指令ng-repeat的优先级为1000,而ng-init的优先级为 450。异步

3. terminal

布尔型,可选参数,能够被设置为true或者false,若设置为true,则优先级低于此指令的其余指令则无效,不会被调用优先级相同任然会执行。函数

4. template

字符串或者函数,可选参数。post

能够是一段html文本

app.directive('hello', function() {
  return {
    template: '<div><h3>Hello, world!</h3></div>'
  }
})

使用以下

<hello></hello>

渲染结果为

<hello>
  <div>
    <h3>Hello, world!</h3>
  </div>
</hello>

也能够是一个函数,可接受两个参数Element与Attrs

其中Element是指使用此指令的元素,而Attrs则是实例的属性,它是由一个元素上全部属性组成的集合,形如

{
  title: 'test',
  id: 'testDiv',
  class: 'demo',
  input: 'text',
  ...
}

下面让咱们看看template是一个函数时的状况

app.directive('hello', function() {
  return {
    template: function(element, attrs) {
      return '<div>'+ attrs.title +'</div>'
    }
  };
});

html代码

<hello title="message"></hello>

渲染结果

<hello>
  <div>message</div>
</hello>

实例地址

5. replace

布尔型,默认值为false,设置为true的时候,表示能够用模板内容替换自定义的元素标签。

在template的例子中,咱们发现渲染结果中包含有自定义的元素<hello></hello>,很显然,这并非咱们想要的渲染结果,所以将replace设置为true以后,hello标签将会消失

app.directive('hello', function() {
  return {
    replace: true,
    template: function(element, attrs) {
      return '<div>'+ attrs.title +'</div>'
    }
  };
});

渲染结果以下,hello标签消失不见

<div>message</div>

6. templateUrl

字符串或者函数,可选参数

可使一个表明html文件路径的字符串,也能够是一个函数,大体意思与template同样。

在本地开发时,须要运行一个服务器,否则使用templateUrl会报错

Cross origin request script(cors)

因为加载html模板是经过异步加载,若加载大量的模板会拖慢网站的速度,这里有一个技巧,就是先缓存模板,你能够先在你的index页面加载好,将下列代码做为你页面的一部分包含在内

<script type="text/ng-template" id="demo.html">
  <div><!--这里是模板内容--></div>
</script>

这里的id属性就是被设置在templateUrl上用的

另一种方法缓存以下

app.run(function($templateCache) {
  $templateCache.put('template.html', '<div>template</div>');
})

7. scope

布尔值或者对象,可选参数,默认值为false,表示继承父级做用域。

若是值为true,表示继承父做用域,并建立本身的做用域(子做用域)

若是为对象,{},则表示建立一个全新的隔离做用域。

首先咱们须要来了解一下scope的继承机制。咱们使用ng-controller这个指令来举例。咱们都知道ng-controller能够从父做用域中继承并建立一个新的子做用域。以下:

<div ng-app="app" ng-init="aaa='parent'">
    parentNode: {{aaa}}
    
    <div ng-controller="demoController">
        childrenNode: {{aaa}}
    </div>
</div>
angular.module('app', [])
.controller('demoController', function($scope) {
    $scope.aaa = "children";
})

实例地址

这时页面的显示结果为

parentNode: parent
childrenNode: children

当时当咱们并无在demoController的做用域中给aaa赋值,也就是在上例中删除这一句$scope.aaa = "children";,那么执行结果就为

parentNode: parent
childrenNode: parent

注意:若是一个元素上有多个指令都使用了隔离做用域,那么只有其中一个能够生效,只有指令模板中的根元素才能得到一个新的做用域,这时候,scope就被设置为true了。

<div ng-app="app" ng-init="aaa='parent'">
    parentNode: {{aaa}}
    
    <div ng-controller="demoController01">
        childrenNode: {{aaa}}
        
        <div ng-controller="demoController02">
            lastNode: {{aaa}}
        </div>
    </div>
</div>
angular.module('app', [])
.controller('demoController01', function($scope) {
    $scope.aaa = "children";
})
.controller('demoController02', function($scope) {
    $scope.aaa = "last"
})

实例地址

接下来,咱们经过一个简单明了的例子来讲明scope取值的不一样差异

<div ng-app="app" ng-controller="mainController">
    parent: {{ name }} 
    <br />
    <input type="text" ng-model="name" />
    <div my-directive></div>
</div>
angular.module('app', [])
.controller('mainController', function($scope) {
    $scope.name = "Jake";
})
.directive('myDirective', function() {
    return {
        restrict: 'EA',
        scope: false,
        replace: true,
        template: '' +
            '<div>' +
                'childNode: {{ name }} ' +
                '<br />' +
                '<input type="text" ng-model="name">' +
            '</div>'
    }
})

实例地址

点击上面的实例地址,咱们能够依次改变scope的值为false, true, {},结果发现

  • false:儿子继承父亲的值,改变父亲的值,儿子的值也随着改变,反之亦然,这就是继承且不隔离
  • true:儿子继承父亲的值,改变父亲的值,儿子的值也随着改变,可是改变儿子的值,父亲的值并无改变,这就是继承可是隔离
  • {}:没有继承父亲的值,因此儿子的值为空,改变任何一方的值都不会影响另外一方,这就是不继承且隔离

当想要建立一个可重用的组件时,隔离做用域是一个很好的选择,经过隔离做用域,咱们能够确保指令是独立的,而且能够轻松的插入到任何HTML APP中,而且这种作法防止了父做用域被污染。

隔离做用域能够经过绑定策略来访问父做用域的属性

咱们来看一个例子

<div ng-app="app" ng-controller="mainController">
    <input type="text" ng-model="color" placeholder="Enter a color"/>
    <hello-world></hello-world>
</div>
angular.module('app', [])
.controller('mainController', function($scope) {
})
.directive('helloWorld', function() {
    return {
        restrict: 'EA',
        scope: false,
        replace: true,
        template: '<p style="background-color:{{color}}">Hello world!</p>'
    }
})

运行上面的代码,咱们在input中输入颜色值,好比red,那么hello world一行的背景就会变成红色。动手试试!

实例地址

可是,当咱们将scope的值设置为{}时,再次运行代码就发现页面并不能成功的完整显示了.这是由于{}让helloWorld指令产生了隔离做用域,没办法从父级做用域中继承到color的值了。

因此在template中的{{color}}变成了依赖于本身的做用域,而不是依赖于父级做用域。所以咱们须要一些办法来让隔离做用域可以读取父级做用域的属性,这个方法就是绑定策略。

下面咱们来探索设置这种绑定策略的几种方法

方法一 使用@来进行单向文本(字符串)绑定
<div ng-app="app" ng-controller="mainController">
    <input type="text" ng-model="color" placeholder="Enter a color"/>
    <hello-world color-attr="{{color}}"></hello-world>
</div>
angular.module('app', [])
.controller('mainController', function($scope) {
})
.directive('helloWorld', function() {
    return {
        restrict: 'EA',
        scope: {
            color: '@colorAttr'
        },
        replace: true,
        template: '<p style="background-color:{{color}}">Hello world!</p>'
    }
})

实例地址

这种办法只能单向,经过在运行的指令DOM上设置color-attr属性,而且采用{{}}绑定某个模型值。固然,咱们也能够直接在这里绑定字符串的颜色值,如color-attr="red"

所以当表达式的值发生变化时,属性color-attr的值也会发生变化,经过单向绑定该值,就能够改变隔离做用域中的属性color.

若是绑定的隔离做用域属性名与元素的属性名相同,则能够采用缺省写法

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

// js
app.directive('helloWorld', function() {
   return {
       scope: { color: '@' },
       ...
   }
})
方法2、使用'='进行双向绑定
<div ng-app="app" ng-controller="mainController">
    <input type="text" ng-model="color" placeholder="Enter a color"/>
    <br />
    {{ color }}
    <!-- 这里要注意写法与@绑定的不一样 -->
    <hello-world color="color"></hello-world>
</div>
angular.module('app', [])
.controller('mainController', function($scope) {
    $scope.color = 'red';
})
.directive('helloWorld', function() {
    return {
        restrict: 'EA',
        scope: {
            color: '='
        },
        replace: true,
        template: '<div><p style="background-color:{{color}}">' +
            'Hello world!' +
        '</p>' + 
        '<input type="text" ng-model="color"></div>'
    }
})

实例地址

此处也采用了相似的缺省写法。

这里须要注意的是,咱们要直接在指令元素设置属性时,是直接将实际的做用域模型复制给该属性

这样一个双向绑定被创建了,改变任何一个input值都会改变另一个值。

方法3、使用'&'调用父做用域中的函数
<div ng-app="app" ng-controller="mainController">
    <input type="text" ng-model="name" placeholder="Enter name"/>
    <br />
    {{ name }}
    <hello-world say="say()" name="{{name}}"></hello-world>
</div>
angular.module('app', [])
.controller('mainController', function($scope) {
    $scope.name = "yangbo";
    $scope.say = function() {
        alert('hello!');
    }
})
.directive('helloWorld', function() {
    return {
        restrict: 'EA',
        scope: {
            name: '@',
            say: '&'
        },
        replace: true,
        template: '<button type="button" ng-bind="name" ng-init="say()"></button>'
    }
})

实例地址

一样采用了缺省写法,运行以后,弹出窗口!

8. transclude

布尔值或者字符element,默认值为false

这个配置选项可让咱们提取包含在指令那个元素里面的内容,再将它放置在指令模板的特定位置。当咱们开启transclude以后,咱们就可使用ng-transclude来指明应该在什么地方放置transclude的内容

<div ng-app="app" ng-controller="mainController">
    <div class="a">
        <p>china</p>
        <hello-world>
            {{name}}
        </hello-world>
    </div>
</div>
angular.module('app', [])
.controller('mainController', function($scope) {
    $scope.name = "yangbo5207";
})
.directive('helloWorld', function() {
    return {
        restrict: 'EA',
        scope: {},
        replace: true,
        transclude: true,
        template: '<div class="b"><div ng-transclude>你看不见我</div></div>'
    }
})

运行上面的代码,输出

china
yangbo5207

咱们查看渲染出来的html,结果为

<div ng-app="app" ng-controller="mainController" class="ng-scope">
    <div class="a">
        <p>china</p>
        <div class="b ng-isolate-scope">
            <div ng-transclude="">
                <span class="ng-binding ng-scope">yangbo5207</span>
            </div>
        </div>
    </div>
</div>

另外当开启transclude时,会建立一个新的transclude空间,而且继承父做用域(也就是scope设置的隔离做用域)

从上面例子咱们知道,当transclude的值被设置为true时,嵌入的内容为{{name}},可是若是将它的值设置为element呢,咱们能够在上例的基础上进行一个简单的修改,发现,嵌入内容为整个元素

<hello-world>{{name}}</hello-world>

查看渲染结果

<div ng-app="app" ng-controller="mainController" class="ng-scope">
    <div class="a">
        <p>china</p>
        <div class="b ng-isolate-scope">
          <div ng-transclude="">
            <hello-world class="ng-binding ng-scope">
              yangbo5207
            </hello-world>
          </div>
        </div>
    </div>
</div>

注意:在一个指令的模板中,只能声明一次ng-transclude

那么问题来了,若是咱们想要把嵌入内容屡次放入咱们的模板中怎么办?

可使用$transclude,后面会讲到!也可使用complie函数中,里面的transcludeFn参数,后面会讲到!或者使用link链接函数

9. controller

能够是一个字符串或者函数。

若为字符串,则将字符串当作控制器的名字,来查找注册在应用中的控制器的构造函数

angular.module('app', [])
.directive('myDirective', function() {
    return {
        restrict: 'EA',
        replace: true,
        transclude: true,
        
        // 会查找模块中其余被名为targetController的控制器
        controller: 'targetController'
    }
})
.controller('targetController', function($scope, $element, $attrs, $transclude) {
    // 控制器逻辑放在这里
})

固然,也能够直接在指令内部定义为匿名函数,一样能够注入任何服务

angular.module('app', [])
.directive('myDirective', function() {
    return {
        restrict: 'EA',
        replace: true,
        transclude: true,
        controller: function($scope, $element, $attrs, $transclude) {
            // 控制器逻辑
        }
    }
})

另外还有一些特殊的服务能够注入,

  • $scope 与指令元素相关联的做用域
  • $element 当前指令对应的元素
  • $attrs 当前元素的属性组成的对象
  • $transclude 嵌入连接函数,实际被执行用来克隆元素和操做DOM中的函数(除非是用来定义一些可复用的行为,不然通常不推荐在这使用)

指令的控制器和link函数(后面会讲到)能够进行互换。区别在于,控制器主要用来提供可在指令间复用的行为,可对外提供与外部交互的接口,可是link连接只能在当前指令内部中定义行为,且没法在指令间复用。

<div ng-app="app">
    <div test-directive="dateType" url="http://www.tigerbrokers.com">外面的世界很精彩。</div>
</div>
angular.module('app', [])
.directive('testDirective', function() {
    return {
        transclude: true,
        replace: true,
        controller: function($scope, $element, $attrs, $transclude, $log) {
            $transclude(function(clone) {
                var a = angular.element('<a>');
                a.attr('href', $attrs.url);
                a.text(clone.text());
                $element.append(a);
            });
        }
    }
})

实例地址

$transclude 能够接受两个参数,第一个是$scope,第二个是才有参数clone的回调函数。

而这个clone实际上就是嵌入的内容。能够在根据它作不少DOM操做。

它还有最简单的用法

angular.module('app', [])
.directive('testDirective', function() {
    return {
        transclude: true,
        replace: true,
        controller: function($scope, $element, $attrs, $transclude, $log) {
            
            // 这里的$transclude就是嵌入的内容
            var a = $transclude();
            $element.append(a);
        }
    }
})

可是咱们要注意,使用$transclude会生成一个新的做用域。默认状况下,若是咱们简单使用$transclude(),那么其做用域就是$transclude 生成的做用域。可是若是咱们使用$transclude($scope, function(clone) {}),那么做用域就是directive的做用域了。

固然问题又来了,若是咱们想使用父级做用域呢?

$scope.$parent
<div ng-app="app" ng-controller="parentController">
    <div ng-controller="sonController">
        <test-directive url="http://www.tigerbrokers.com">炒美股,上老虎2</test-directive>
    </div>
</div>
angular.module('app', [])
.controller('parentController', function($scope) {
    $scope.title = "PARENT TIGER";
})

.controller('sonController', function($scope) {
    $scope.title = "CHILDREN TIGER";
})

.directive('testDirective', function() {
    return {
        transclude: true,
        replace: true,
        controller: function($scope, $element, $attrs, $transclude, $log) {
            var a = $transclude();
            $element.append(a);
            $log.info($scope.title);
            $log.info($scope.$parent.title);
        }
    }
})

实例地址

10. controllerAs

angualr 1.2以后,就已经开始支持controllerAs的语法,这样咱们就能够不用将属性和方法挂载到$scope上,而是this上。

<div ng-app="app" ng-controller="demoController as demo">{{ demo.name }}</div>
angular.module('app', [])
.controller('demoController', function() {
    this.name = "Jake";
})

实例地址

咱们能够从实例中看出,这里的this就是指的as后面的那个别名。而后咱们在表达式中就可使用这个别名

咱们知道,在directive中的controller,主要职能是对外提供交互接口,而结合require与link,所以咱们经常会利用这样的语法而非上面例子中的$scope

app.directive('testDirective', function() {
    return {
        controller: function() {
            this.name = "Jake";
        }
    }
})

11. require

字符串或者数组,字符串表明另外一个指令的名字,它将做为link函数的第四个参数。具体用法咱们能够举例子来讲明。

假设如今咱们要编写两个指令,两个指令的link函数中存在许多重合的方法,这时候咱们就能够将这些重复的方法写在第三个指令的controller中,而后在这两个指令中,使用require将第三个指令引入进来,而后咱们就能够经过link链接函数的第四个参数引用这些重合的方法了

指令之间经常经过这种方式进行交互

<div ng-app="expanderModule" ng-controller="SomeController" class="wrap">
    <accordion>
        <expander class='expander' ng-repeat='expander in expanders' expander-title='expander.title'>
            {{expander.text}}
        </expander>
    </accordion>
</div>
var expModule=angular.module('expanderModule',[])
expModule.directive('accordion', function() {
    return {
        restrict : 'EA',
        replace : true,
        transclude : true,
        template : '<div ng-transclude></div>',
        controller : function() {
            var expanders = [];
            this.gotOpened = function(selectedExpander) {
                angular.forEach(expanders, function(expander) {
                    if (selectedExpander != expander) {
                        expander.showMe = false;
                    }
                });
            }
            this.addExpander = function(expander) {
                expanders.push(expander);
            }
        }
    }
});

expModule.directive('expander', function() {
    return {
        restrict : 'EA',
        replace : true,
        transclude : true,
        require : '^?accordion',
        scope : {
            title : '=expanderTitle'
        },
        template : '<div>'
                   + '<div class="title" ng-click="toggle()">{{title}}</div>'
                   + '<div class="body" ng-show="showMe" ng-transclude></div>'
                   + '</div>',
        link : function(scope, element, attrs, accordionController) {
            scope.showMe = false;
            accordionController.addExpander(scope);
            scope.toggle = function toggle() {
                scope.showMe = !scope.showMe;
                accordionController.gotOpened(scope);
            }
        }
    }
});

expModule.controller("SomeController",function($scope) {
    $scope.expanders = [{
        title : 'Click me to expand',
        text : 'Hi there folks, I am the content that was hidden but is now shown.'
    }, {
        title : 'Click this',
        text : 'I am even better text than you have seen previously'
    }, {
        title : 'Test',
        text : 'test'
    }];
});

实例地址

实例说明,controller是用来与不一样指令之间通讯的。

另外咱们能够在require的参数值加上下面的某个前缀,这回改变查找控制器的行为。

  • 没有前缀 指令会在自身提供的控制器中进行查找,若是找不到任何控制器,则会抛出一个error
  • ? 若在当前指令中没有找到所需的控制器,则会将null传给link连接函数的第四个参数
  • ^ 若是在当前的指令中没有找到所需的控制器,则会查找父元素的控制器
  • ?^ 若是在当前和父元素中都没有找到控制器,则会将null传给link

12. angular指令编译过程

首先加载angular库,查找ng-app指令,从而找到应用的边界,根据ng-app划定的做用域来调用$compile服务进行编译。

angular会遍历整个html文档,并根据js中指令的定义来处理在页面上声明的各个指令,按照指令的优先级排列,根据指令中的配置参数(template, replace, transclude等)转换DOM,而后就开始按照顺序执行各指令的compile函数(若是指令上有定义compile函数)对模板自身进行转换。

此处的compile函数值什么指令中配置的,与上面说的$compile服务不同

每一个compile函数执行完后会返回一个link函数,全部的link函数会合成一个大的link函数,而后这个大的link函数就会被执行, 主要作数据绑定,经过 DOM上注册监听器来动态修改scope中的数据,或者是使用$watchs监听scope中变量来修改DOM,从而创建双向绑定等等。

若咱们的指令中没有配置compile函数,那咱们配置的link函数就会运行,她作的事情大体跟上面compile返回以后全部的link合成的大link函数差很少。

因此,在指令中compile与link选项是互斥的,若是同时设置了这两项,那么就会把compile所返回的函数当作是连接函数,而link选项自己会被忽略。

13. compile编译函数与link连接函数

cmopile选项能够返回一个对象或者函数,在这里咱们能够在指令和实时数据被放到DOM以前进行DOM操做,好比咱们能够在这里进行添加或者删除节点的DOM操做。

因此编译函数是负责对模板的DOM进行转换,而且仅仅只会运行一次

//compile函数的语法
compile:function compile(tElement,tAttrs,transclude){
      return{
        pre:function preLink(scope,iElement,iAttrs,controller){},
        post:function postLink(scope,iElement,iAttrs,controller){}
      }
    }

对于咱们编写的大部分指令来讲,并不须要对模板进行转换,因此大部分状况只须要编写link函数就行。

preLink函数会在编译阶段以后,指令连接到子元素以前执行,相似的,postLink会在指令连接到子元素以后执行。这意味着,为了避免破坏绑定过程,若是你须要修改DOM结构,你应该在postLink函数中来作这件事情。

link函数负责将做用域与DOM进行连接

//link连接函数
link:function postLink(scope,iElement,iAttrs){}

若指令中定义有require选项,则link函数会有第四个参数,表明控制器或者所依赖的指令的控制器(上面require选项例子已有例子)

内容都真够多的,终于整理完了,今天写了两篇文章,感受好累!若是你们以为文章对你有帮助,求赞求收藏。

许多内容都是从不一样的网站上整理而来,每个实例都是本身运行事后保存在codepen上,你们能够结合codepen的例子结合理解文章内容,本文属于收集文,非原创,但绝对干货!值!得!一!赞!与收藏。

clipboard.png

相关文章
相关标签/搜索