• AngularJs自定义指令详解(5)


    在指令中操作DOM,我们需要link参数,这参数要求声明一个函数,称之为链接函数。

    写法:

    link: function(scope, element, attrs) {
      // 在这里操作DOM
    }

    如果指令使用了require选项,那么链接函数会有第四个参数,代表控制器或者所依赖的指令的控制器。

    // require 'SomeController',
    link: function(scope, element, attrs, SomeController) {
      // 在这里操作DOM,可以访问required指定的控制器
    }

    链接函数之所以能够在指令中操作DOM,就是因为传入的这几个参数:scope、element、attrs

    scope:即与指令元素相关联的当前作用域,可以用来注册监听器:scope.$watch()

    element:即当前指令对应的元素,使用它可以操作该元素及其子元素。例如<span my-directive></span>,这个span就是指令 my-directive所使用的元素。

    attrs:由当前元素的属性组成的对象。

    下面看一个例子,来自官方文档的示例。

    我们要实现一个时钟,根据给定的时间格式显示当前的时间,而且每隔一秒要更新一次时间。

    首先在控制器中初始化一个时间格式:

    controller('Controller', ['$scope', function($scope) {
        $scope.format = 'M/d/yy h:mm:ss a';
    }])

    对于时间格式,显然我们要引入$filter服务。

    对于”每隔一秒“进行某些操作,显然要引入$interval服务。

    为了测试程序,我们还引入$log服务以便在浏览器中观察输出。

    所以自定义的指令需要写成这样:

    directive('myClock', ['$interval', '$filter', '$log', function($interval, $filter,$log)

    这个myClock指令将会被注入$interval、$filter、$log服务。

    刷新时间显示,也就是要求我们在指令中操作DOM,输出时间:

    function updateTime() {
      element.text($filter('date')(new Date(), interFormat));
    }

    $filter方法的使用:

    $filter('date')(date, format, timezone)

    参考https://code.angularjs.org/1.3.16/docs/api/ng/filter/date

    每隔一秒刷新显示:

    $interval(

      function() {
        updateTime();
      },

      1000

    );

    完整代码如下:

    复制代码
    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <script src="../lib/angular-1.3.16/angular.min.js"></script>
        <script src=""></script>
        <title></title>
        <script language="JavaScript">
            angular.module('docsTimeDirective', [])
                    .controller('Controller', ['$scope', function($scope) {
                        $scope.format = 'M/d/yy h:mm:ss a';
                    }])
                    .directive('myClock', ['$interval', '$filter', '$log', function($interval, $filter,$log) {
                        return {
                            scope:{
                                myFormat:'='
                            },
                            link: function(scope, element, attrs) {
                                function updateTime() {
                                    element.text($filter('date')(new Date(), scope.myFormat));
                                }
                                updateTime();
                                $interval(function() {
                                    updateTime();
                                }, 1000);
                            }
                        };
                    }]);
        </script>
    </head>
    <body ng-app="docsTimeDirective">
    <div ng-controller="Controller">
        时间格式: <input ng-model="format"> <hr/>
        当前时间: <span my-clock my-format="format"></span>
    </div>
    </body>
    </html>
    复制代码

    运行效果:

    不过我们很快就发现一个问题,就是修改时间格式后,无法立刻刷新时间显示,把每隔一秒修改为每隔五秒,问题就更加明显了。

    虽然修改format会因为双向绑定而使myFormat发生变化,但是后者并不会触发执行updateTime()函数。

    所以需要加入$watch监听:

    复制代码
    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <script src="../lib/angular-1.3.16/angular.min.js"></script>
        <script src=""></script>
        <title></title>
        <script language="JavaScript">
            angular.module('docsTimeDirective', [])
                    .controller('Controller', ['$scope', function($scope) {
                        $scope.format = 'M/d/yy h:mm:ss a';
                    }])
                    .directive('myClock', ['$interval', '$filter', '$log', function($interval, $filter,$log) {
                        return {
                            scope:{
                                myFormat:'='
                            },
                            link: function(scope, element, attrs) {
                                function updateTime() {
                                    element.text($filter('date')(new Date(), scope.myFormat));
                                }
                                scope.$watch('myFormat',function(newValue) {
                                    $log.info('value changed to ' + newValue);
                                    updateTime();
                                 });
                                $interval(function() {
                                    updateTime();
                                }, 1000);
                            }
                        };
                    }]);
        </script>
    </head>
    <body ng-app="docsTimeDirective">
    <div ng-controller="Controller">
        时间格式: <input ng-model="format"> <hr/>
        当前时间: <span my-clock my-format="format"></span>
    </div>
    </body>
    </html>
    复制代码

    注意$watch()的第一个参数为'myFormat',不要少了单引号,也不要写成'scope.myFormat'、'$scope.myFormat',要不然newValue的值是undefined了。

    还有就是删掉了外面的updateTime()调用,因为在$watch里myFormat第一次绑定时,已经触发监听器的回调函数了,于是updateTime()也立刻执行。

    上面的代码监听的是定义在指令的隔离作用域上的myFormat,而官方文档监听的是DOM中span元素的my-format属性,效果是差不多的,代码如下:

    复制代码
    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <script src="../lib/angular-1.3.16/angular.min.js"></script>
        <script src=""></script>
        <title></title>
        <script language="JavaScript">
            angular.module('docsTimeDirective', [])
                    .controller('Controller', ['$scope', function($scope) {
                        $scope.format = 'M/d/yy h:mm:ss a';
                    }])
                    .directive('myClock', ['$interval', '$filter', '$log', function($interval, $filter,$log) {
                        return {
                            link: function(scope, element, attrs) {
                                var interFormat, timeoutId;
    
                                function updateTime() {
                                    element.text($filter('date')(new Date(), interFormat));
                                }
    
                                scope.$watch(attrs.myFormat, function(value) {
                                    interFormat = value;
                                    updateTime();
                                });
    
                                element.on('$destroy', function() {
                                    $interval.cancel(timeoutId);
                                });
    
                                timeoutId = $interval(function() {
                                    updateTime();
                                }, 1000);
                            }
                        };
                    }]);
        </script>
    </head>
    <body ng-app="docsTimeDirective">
    <div ng-controller="Controller">
        时间格式: <input ng-model="format"> <hr/>
        Current time is: <span my-clock my-format="format"></span>
    </div>
    </body>
    </html>
    复制代码

    官方文档指出一个问题:$interval注册的匿名函数不会在元素被移除时自动释放,存在一定的内存泄露风险,所以增加了代码:

    element.on('$destroy', function() {
      $interval.cancel(timeoutId);
    });

    这三行代码也演示了如何在指令内加入对元素的事件监听器,官方文档还提供了另一个例子,代码如下:

    复制代码
    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <script src="../lib/angular-1.3.16/angular.min.js"></script>
        <script src=""></script>
        <title></title>
        <script language="JavaScript">
            angular.module('dragModule', [])
                    .directive('myDraggable', ['$document', function($document) {
                        return {
                            link: function(scope, element, attr) {
                                var startX = 0, startY = 0, x = 0, y = 0;
    
                                element.css({
                                    position: 'relative',
                                    border: '1px solid blue',
                                    backgroundColor: 'yellow',
                                    cursor: 'pointer'
                                });
    
                                element.on('mousedown', function(event) {
                                    // Prevent default dragging of selected content
                                    event.preventDefault();
                                    startX = event.pageX - x;
                                    startY = event.pageY - y;
                                    $document.on('mousemove', mousemove);
                                    $document.on('mouseup', mouseup);
                                });
    
                                function mousemove(event) {
                                    y = event.pageY - startY;
                                    x = event.pageX - startX;
                                    element.css({
                                        top: y + 'px',
                                        left:  x + 'px'
                                    });
                                }
    
                                function mouseup() {
                                    $document.off('mousemove', mousemove);
                                    $document.off('mouseup', mouseup);
                                }
                            }
                        };
                    }]);
        </script>
    </head>
    <body ng-app="dragModule">
    <span my-draggable>Drag ME</span>
    </body>
    </html>
    复制代码

     现在回顾一下前面文章提到的隔离作用域问题,看看以下代码:

    复制代码
    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <script src="../lib/angular-1.3.16/angular.min.js"></script>
        <script src=""></script>
        <title></title>
        <script language="JavaScript">
            angular.module('app',[])
                    .directive('myDirective',function(){
                        return{
                            template:'Hello {{greeting}}!',
                            //scope:{ },
                            link:function(scope,element,attrs){
                                scope.greeting = 'AngularJs';
                            }
                        };
                    });
        </script>
    </head>
    <body ng-app="app">
    <div ng-init="greeting='World'"></div>
    1,<span>Hello {{greeting}}!</span><hr>
    2,<span my-directive></span><hr>
    </body>
    </html>
    复制代码

    输出:

    1,Hello AngularJs!


    2,Hello AngularJs!

    虽然greeting被初始化为'World',但是在链接函数里修改成'AngularJs’,可见此时传给链接函数的scope是上一级作用域(在这里是rootScope)

    这造成了污染,一般情况下我们不希望指令不声不响地修改外面的变量,解决办法是把代码里//scope:{}的注释去掉,隔离指令的作用域。

    于是输出就会变成:

    1,Hello World!


    2,Hello AngularJs!

    再看下面的代码:

    复制代码
    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <script src="../lib/angular-1.3.16/angular.min.js"></script>
        <script src=""></script>
        <title></title>
        <script language="JavaScript">
            angular.module('app',[])
                    .directive('myDirective',function(){
                        return{
                            restrict:'E',
                            template:'<span ng-transclude></span>',
                            scope:{ },
                            transclude: true,
                            link:function(scope,element,attrs){
                                scope.greeting = 'AngularJs';
                            }
                        };
                    });
        </script>
    </head>
    <body ng-app="app">
    <div ng-init="greeting='World'"></div>
    1,<span>Hello {{greeting}}!</span><hr>
    2,<my-directive>Hello {{greeting}}!</my-directive><hr>
    </div>
    </body>
    </html>
    复制代码

    输出:

    1,Hello World!


    2,Hello World!

    即使ng-transclude指令放在指令定义的模板中,但是{{greeting}}绑定放在外面,而指令已经隔离了作用域,所以{{greeting}}使用的是外面的'World'。

    如果注释掉scope:{},指令的作用域没有隔离,于是输出就变为:

    1,Hello AngularJs!


    2,Hello AngularJs!

  • 相关阅读:
    ES6中的类
    promise小案例
    Promise.resolve()与Promise
    Promise.resolve( data)与Promise.reject( data )
    Promise.race()
    Promise.all()
    咦?浏览器又崩了?再试试这个呢!
    页面太卡了?试试这方法呢!
    js进阶之重复的定时器
    关于vue+element-ui项目的分页,返回默认显示第一页的问题解决
  • 原文地址:https://www.cnblogs.com/minghui007/p/7160439.html
Copyright © 2020-2023  润新知