zoukankan      html  css  js  c++  java
  • 走进AngularJs(五)自定义指令----(下)

      自定义指令学习有段时间了,学了些纸上谈兵的东西,还没有真正的写个指令出来呢。。。所以,随着学习的接近尾声,本篇除了介绍剩余的几个参数外,还将动手结合使用各参数,写个真正能用的指令出来玩玩。

      我们在自定义指令(上)中,写了一个简单的<say-hello></say-hello>,能够跟美女打招呼。但是看看人家ng内置的指令,都是这么用的:ng-model=”m”,ng-repeat=”a in array”,不单单是作为属性,还可以赋值给它,与作用域中的一个变量绑定好,内容就可以动态变化了。假如我们的sayHello可以这样用:<say-hello speak=”content”>美女</say-hello>,把要对美女说的话写在一个变量content中,然后只要在controller中修改content的值,页面就可以显示对美女说的不同的话。这样就灵活多了,不至于见了美女只会说一句hello,然后就没有然后了。

      为了实现这样的功能,我们需要使用scope参数,下面来介绍一下。

    使用scope为指令划分作用域

      顾名思义,scope肯定是跟作用域有关的一个参数,它的作用是描述指令与父作用域的关系,这个父作用域是指什么呢?想象一下我们使用指令的场景,页面结构应该是这个样子:

    <div ng-controller="testC">
        <say-hello speak="content">美女</say-hello>
    </div>

      外层肯定会有一个controller,而在controller的定义中大体是这个样子:

    var app = angular.module('MyApp', [], function(){console.log('here')});
    app.controller('testC',function($scope){
    $scope.content = '今天天气真好!';
    });

      所谓sayHello的父作用域就是这个名叫testC的控制器所管辖的范围,指令与父作用域的关系可以有如下取值:

    取值

    说明

    false

    默认值。使用父作用域作为自己的作用域

    true

    新建一个作用域,该作用域继承父作用域

    javascript对象

    与父作用域隔离,并指定可以从父作用域访问的变量

      乍一看取值为false和true好像没什么区别,因为取值为true时会继承父作用域,即父作用域中的任何变量都可以访问到,效果跟直接使用父作用域差不多。但细细一想还是有区别的,有了自己的作用域后就可以在里面定义自己的东西,与跟父作用域混在一起是有本质上的区别。好比是父亲的钱你想花多少花多少,可你自己挣的钱父亲能花多少就不好说了。你若想看这两个作用域的区别,可以在link函数中打印出来看看,还记得link函数中可以访问到scope吧。

      最有用的还是取值为第三种,一个对象,可以用键值来显式的指明要从父作用域中使用属性的方式。当scope值为一个对象时,我们便建立了一个与父层隔离的作用域,不过也不是完全隔离,我们可以手工搭一座桥梁,并放行某些参数。我们要实现对美女说各种话就得靠这个。使用起来像这样:

    scope: {
            attributeName1: 'BINDING_STRATEGY',
            attributeName2: 'BINDING_STRATEGY',...
    }

      键为属性名称,值为绑定策略。等等!啥叫绑定策略?最讨厌冒新名词却不解释的行为!别急,听我慢慢道来。

      先说属性名称吧,你是不是认为这个attributeName1就是父作用域中的某个变量名称?错!其实这个属性名称是指令自己的模板中要使用的一个名称,并不对应父作用域中的变量。好难懂啊。。。可能这么说太不负责任了,稍后的例子中我们来说明。再来看绑定策略,它的取值按照如下的规则:

    符号

    说明

    举例

    @

    传递一个字符串作为属性的值.

    str : ‘@string’

    =

    使用父作用域中的一个属性,绑定数据到指令的属性中.

    name : ‘=username’

    &

    使用父作用域中的一个函数,可以在指令中调用

    getName : ‘&getUserName’

      总之就是用符号前缀来说明如何为指令传值。你肯定迫不及待要看例子了,我们结合例子看一下,小二,上栗子~

    举例说明

      我想要实现上面想像的跟美女多说点话的功能,即我们给sayHello指令加一个属性,通过给属性赋值来动态改变说话的内容。主要代码如下:

    app.controller('testC',function($scope){
            $scope.content = '今天天气真好!';
        });
    app.directive('sayHello',function(){
        return {
            restrict : 'E',
            template : '<div>hello,<b ng-transclude></b>,{­{cont}­}</div>',
            replace : true,
            transclude : true,
            scope : {
                 cont : '=speak'
             }
        };
    });

      然后在模板中,我们如下使用指令:

    <div ng-controller="testC">
        <say-hello speak="content">美女</say-hello>
    </div>

      看看运行效果:

    美女

      执行的流程是这样的:

      ① 指令被编译的时候会扫描到template中的{ {cont} },发现是一个表达式;

      ② 查找scope中的规则:通过speak与父作用域绑定,方式是传递父作用域中的属性;

      ③ speak与父作用域中的content属性绑定,找到它的值“今天天气真好!”

      ④ 将content的值显示在模板中

      这样我们说话的内容cont就跟父作用域绑定到了一其,如果动态修改父作用域的content的值,页面上的内容就会跟着改变,正如你点击“换句话”所看到的一样。

      这个例子也太小儿科了吧!简单虽简单,但可以让我们理解清楚,为了检验你是不是真的明白了,可以思考一下如何修改指令定义,能让sayHello以如下两种方式使用:

    <span say-hello speak="content">美女</span>

    <span say-hello="content" >美女</span>

      答案我就不说了,简单的很。下面有更重要的事情要做,我们说好了要写一个真正能用的东西来着。接下来就结合所学到的东西来写一个折叠菜单,即点击可展开,再点击一次就收缩回去的菜单,(偷偷告诉你,这个例子其实是从大漠穷秋的书上抄来的~)。

      控制器及指令的代码如下:(为了不让文章太长,我后面的代码要折叠起来了,请自行点开)

    app.controller('testC',function($scope){
            $scope.title = '个人简介';
            $scope.text = '大家好,我是一名前端工程师,我正在研究AngularJs,欢迎大家与我交流,Email:lvxiaobao_fbi@163.com';
        });
        app.directive('expander',function(){
            return {
                restrict : 'E',
                templateUrl : 'expanderTemp.html',
                replace : true,
                transclude : true,
                scope : {
                    mytitle : '=etitle'
                },
                link : function(scope,element,attris){
                    scope.showText = false;
                    scope.toggleText = function(){
                        scope.showText = ! scope.showText;
                    }
                }
            };
        });
    View Code

      HTML中的代码如下:

    <script id="expanderTemp.html" type="text/ng-template">
    <div class="mybox">
                    <div class="mytitle" ng-click="toggleText()">
                    {{mytitle}}
                    </div>
                    <div ng-transclude ng-show="showText"></div>
    </div>
    </script>

      看看运行效果:

      还是比较容易看懂的,我只做一点必要的解释。首先我们定义模板的时候使用了ng的一种定义方式<script type=”text/ng-template” id="expanderTemp.html">,在指令中就可以用templateUrl根据这个id来找到模板。指令中的{­{mytitle}­}表达式由scope参数指定从etitle传递,etitle指向了父作用域中的title。为了实现点击标题能够展开收缩内容,我们把这部分逻辑放在了link函数中,link函数可以访问到指令的作用域,我们定义showText属性来表示内容部分的显隐,定义toggleText函数来进行控制,然后在模板中绑定好。 如果把showText和toggleText定义在controller中,作为$scope的属性呢?显然是不行的,这就是隔离作用域的意义所在,父作用域中的东西除了title之外通通被屏蔽。

      上面的例子中,scope参数使用了=号来指定获取属性的类型为父作用域的属性,如果我们想在指令中使用父作用域中的函数,使用&符号即可,是同样的原理。

      以上是本人对scope的理解,另外有一篇文章对Angular作用域的解释也比较详细,有兴趣可以参考http://www.angularjs.cn/A09C

    使用controller和require进行指令间通信

      使用指令来定义一个ui组件是个不错的想法,首先使用起来方便,只需要一个标签或者属性就可以了,其次是可复用性高,通过controller可以动态控制ui组件的内容,而且拥有双向绑定的能力。当我们想做的组件稍微复杂一点,就不是一个指令可以搞定的了,就需要指令与指令的协作才可以完成,这就需要进行指令间通信。

      想一下我们进行模块化开发的时候的原理,一个模块暴露(exports)对外的接口,另外一个模块引用(require)它,便可以使用它所提供的服务了。ng的指令间协作也是这个原理,这也正是自定义指令时controller参数和require参数的作用。

      controller参数用于定义指令对外提供的接口,它的写法如下:

    controller: function controllerConstructor($scope, $element, $attrs, $transclude)

      它是一个构造器函数,将来可以构造出一个实例传给引用它的指令。为什么叫controller(控制器)呢?其实就是告诉引用它的指令,你可以控制我。至于可以控制那些东西呢,就需要在函数体中进行定义了。先看controller可以使用的参数,作用域、节点、节点的属性、节点内容的迁移,这些都可以通过依赖注入被传进来,所以你可以根据需要只写要用的参数。关于如何对外暴露接口,我们在下面的例子来说明。

      require参数便是用来指明需要依赖的其他指令,它的值是一个字符串,就是所依赖的指令的名字,这样框架就能按照你指定的名字来从对应的指令上面寻找定义好的controller了。不过还稍稍有点特别的地方,为了让框架寻找的时候更轻松些,我们可以在名字前面加个小小的前缀:^,表示从父节点上寻找,使用起来像这样:require : ‘^directiveName’,如果不加,$compile服务只会从节点本身寻找。另外还可以使用前缀:?,此前缀将告诉$compile服务,如果所需的controller没找到,不要抛出异常。

      所需要了解的知识点就这些,接下来是例子时间,依旧是从书上抄来的一个例子,我们要做的是一个手风琴菜单,就是多个折叠菜单并列在一起,此例子用来展示指令间的通信再合适不过。

      首先我们需要定义外层的一个结构,起名为accordion,代码如下:

    app.directive('accordion',function(){
            return {
                restrict : 'E',
                template : '<div ng-transclude></div>',
                replace : true,
                transclude : true,
                controller :function(){
                    var expanders = [];
                    this.gotOpended = function(selectedExpander){
                        angular.forEach(expanders,function(e){
                            if(selectedExpander != e){
                                e.showText = false;
                            }
                        });
                    }
    
                    this.addExpander = function(e){
                        expanders.push(e);
                    }
                }
            }
        });
    View Code

      需要解释的只有controller中的代码,我们定义了一个折叠菜单数组expanders,并且通过this关键字来对外暴露接口,提供两个方法。gotOpended接受一个selectExpander参数用来修改数组中对应expander的showText属性值,从而实现对各个子菜单的显隐控制。addExpander方法对外提供向expanders数组增加元素的接口,这样在子菜单的指令中,便可以调用它把自身加入到accordion中。

      看一下我们的expander需要做怎样的修改呢:

    app.directive('expander',function(){
            return {
                restrict : 'E',
                templateUrl : 'expanderTemp.html',
                replace : true,
                transclude : true,
                require : '^?accordion',
                scope : {
                    title : '=etitle'
                },
                link : function(scope,element,attris,accordionController){
                    scope.showText = false;
                    accordionController.addExpander(scope);
                    scope.toggleText = function(){
                        scope.showText = ! scope.showText;
                        accordionController.gotOpended(scope);
                    }
                }
            };
        });
    View Code

      首先使用require参数引入所需的accordion指令,添加?^前缀表示从父节点查找并且失败后不抛出异常。然后便可以在link函数中使用已经注入好的accordionController了,调用addExpander方法将自己的作用域作为参数传入,以供accordionController访问其属性。然后在toggleText方法中,除了要把自己的showText修改以外,还要调用accordionController的gotOpended方法通知父层指令把其他菜单给收缩起来。

      指令定义好后,我们就可以使用了,使用起来如下:

    <accordion>
      <expander ng-repeat="expander in expanders" etitle="expander.title">{­{expander.text}­}</expander>
    </accordion>

      外层使用了accordion指令,内层使用expander指令,并且在expander上用ng-repeat循环输出子菜单。请注意这里遍历的数组expanders可不是accordion中定义的那个expanders,如果你这么认为了,说明还是对作用域不够了解。此expanders是ng-repeat的值,它是在外层controller中的,所以,在testC中,我们需要添加如下数据:

    $scope.expanders = [
                {title: '个人简介',
                 text: '大家好,我是一名前端工程师,我正在研究AngularJs,欢迎大家与我交流,Email:lvxiaobao_fbi@163.com'},
                {title: '我的爱好',
                 text: '运动类:篮球、足球、乒乓球。  电脑类:前端技术、打DOTA。  其他类:欣赏美女'},
                {title: '性格及工作',
                 text: '追求完美主义的处女座极品男人就是我啦~严重的代码洁癖以及对垃圾代码的零容忍!希望通过自己的努力进入理想的公司工作。'}
            ];
    View Code

      这下就都全乎了,试一下我们的accordion组件是不是可以正常使用了呢:

      理解了其中的道理之后,使用起来就可以得心应手了,我也将在以后的实践中尝试编写更加复杂的组件,此小例子就当是抛砖引玉了~

    总结

      又到了总结时间,到此为止自定义指令的学习就告一段落了,但我相信相关的知识肯定远远不止这些,真正要将指令在项目中用好,还需要理解指令与ng的其他机制如何相互作用,还需更加深入的了解ng的指令机制等。所以学与用的转变还需要实践的检验。

      撰写博客使我的学习进度变的异常缓慢,要加油了!

  • 相关阅读:
    Http中GET和POST两种请求的区别
    JSON学习笔记
    分页
    python 函数,闭包
    LVS负载均衡中arp_ignore和arp_annonuce参数配置的含义
    return ;
    openssl 在php里
    重装drupal
    protected的意义
    和 和 notepad++
  • 原文地址:https://www.cnblogs.com/lvdabao/p/3407424.html
Copyright © 2011-2022 走看看