AngularJS:绑定到指令中的全局事件的最佳方法是什么

Bas*_*ter 61 javascript events resize directive angularjs

想象一下AngularJS中你想要创建一个需要响应全局事件的指令的情况.在这种情况下,假设窗口调整大小事件.

对此最好的方法是什么?我看到它的方式,我们有两个选择:1.让每个指令绑定到事件并在当前元素上做它的魔术2.创建一个全局事件监听器,它执行DOM选择器以获取逻辑应该在其上的每个元素应用.

选项1的优点是您已经可以访问要执行某些操作的元素.但是......选项2的优点是您不必在同一事件上多次绑定(对于每个指令),这可能是性能优势.

我们来说明两个选项:

选项1:

angular.module('app').directive('myDirective', function(){

     function doSomethingFancy(el){
         // In here we have our operations on the element
    }

    return {
        link: function(scope, element){
             // Bind to the window resize event for each directive instance.
             angular.element(window).on('resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});
Run Code Online (Sandbox Code Playgroud)

选项2:

angular.module('app').directive('myDirective', function(){

    function doSomethingFancy(){
         var elements = document.querySelectorAll('[my-directive]');
         angular.forEach(elements, function(el){
             // In here we have our operations on the element
         });
    }

    return {
        link: function(scope, element){
             // Maybe we have to do something in here, maybe not.
        }
    };

    // Bind to the window resize event only once.
    angular.element(window).on('resize', doSomethingFancy);
});
Run Code Online (Sandbox Code Playgroud)

这两种方法都运行正常,但我觉得选项二并不是'Angular-ish'.

有任何想法吗?

Mic*_*mza 138

我选择了另一种方法来有效地本地化全局事件,比如窗口大小调整.它通过另一个指令将Javascript事件转换为Angular范围事件.

app.directive('resize', function($window) {
  return {
    link: function(scope) {
      function onResize(e) {
        // Namespacing events with name of directive + event to avoid collisions
        scope.$broadcast('resize::resize');
      }

      function cleanUp() {
        angular.element($window).off('resize', onResize);
      }

      angular.element($window).on('resize', onResize);
      scope.$on('$destroy', cleanUp);
    }
  }
});
Run Code Online (Sandbox Code Playgroud)

在基本情况下,可以在应用程序的根元素上使用哪个

<body ng-app="myApp" resize>...
Run Code Online (Sandbox Code Playgroud)

然后在其他指令中监听事件

<div my-directive>....
Run Code Online (Sandbox Code Playgroud)

编码为:

app.directive('myDirective', function() {
  return {
    link: function(scope, element) {
      scope.$on('resize::resize', function() {
        doSomethingFancy(element);
      });
    });
  }
});
Run Code Online (Sandbox Code Playgroud)

与其他方法相比,这有许多好处:

  • 对于如何使用指令的确切形式并不脆弱.您的选项2需要my-directive时的角将其视为等效以下:my:directive,data-my-directive,x-my-directive,my_directive如可以在中可以看出指南关于指示

  • 您只有一个地方可以准确地影响Javascript事件如何转换为Angular事件,然后影响所有侦听器.假设你后来想要resize使用Lodash去抖功能去抖动javascript 事件.您可以将resize指令修改为:

    angular.element($window).on('resize', $window._.debounce(function() {
      scope.$broadcast('resize::resize');
    },500));
    
    Run Code Online (Sandbox Code Playgroud)
  • 因为它不一定会触发事件$rootScope,所以只需移动resize指令的位置即可将事件限制为仅部分应用程序

    <body ng-app="myApp">
      <div>
        <!-- No 'resize' events here -->
      </div>
      <div resize>
        <!-- 'resize' events are $broadcast here -->
      </div>
    
    Run Code Online (Sandbox Code Playgroud)
  • 您可以使用选项扩展指令,并在应用的不同部分以不同方式使用它.假设您需要不同部分的不同去抖版本:

    link: function(scope, element, attrs) {
      var wait = 0;
      attrs.$observe('resize', function(newWait) {
        wait = $window.parseInt(newWait || 0);
      });
      angular.element($window).on('resize', $window._.debounce(function() {
        scope.$broadcast('resize::resize');
      }, wait));
    }
    
    Run Code Online (Sandbox Code Playgroud)

    用作:

    <div resize>
      <!-- Undebounced 'resize' Angular events here -->
    </div>
    <div resize="500">
      <!-- 'resize' is debounced by 500 milliseconds -->
    </div>
    
    Run Code Online (Sandbox Code Playgroud)
  • 您可以稍后使用可能有用的其他事件扩展该指令.也许像resize::heightIncrease.resize::heightDecrease,resize::widthIncrease,resize::widthDecrease.然后,您的应用中有一个位置可以处理记住和处理窗口的确切尺寸.

  • 您可以将数据与事件一起传递.比如您可能需要处理跨浏览器问题的视口高度/宽度(取决于您需要IE支持的距离,以及是否包含另一个库来帮助您).

    angular.element($window).on('resize', function() {
      // From http://stackoverflow.com/a/11744120/1319998
      var w = $window,
          d = $document[0],
          e = d.documentElement,
          g = d.getElementsByTagName('body')[0],
          x = w.innerWidth || e.clientWidth || g.clientWidth,
          y = w.innerHeight|| e.clientHeight|| g.clientHeight;
      scope.$broadcast('resize::resize', {
        innerWidth: x,
        innerHeight: y
      });
    });
    
    Run Code Online (Sandbox Code Playgroud)

    这为您提供了一个稍后添加到数据的位置.例如,假设您要发送自上次去抖事件以来尺寸的差异?您可以添加一些代码来记住旧的大小并发送差异.

本质上,这种设计提供了一种方法,可以以可配置的方式将全局Javascript事件转换为本地Angular事件,而不仅仅局部转换为应用程序,而是本地应用程序的不同部分,具体取决于指令的位置.


小智 6

在框架之上进行开发时,我经常发现在设计一个惯用语之前,对于一个问题进行厌恶思考是有帮助的.回答"什么"和"为什么"驱逐"如何".

答案在这里真的取决于复杂性doSomethingFancy().是否存在与此指令实例关联的数据,一组功能或域对象?这是一个纯粹的表现性问题,比如将某些元素widthheight属性调整到适当比例的窗口大小?确保你正在使用合适的工具; 当工作需要镊子并且您可以使用独立配对时,不要携带整个瑞士军刀.为了继续这种方式,我将doSomethingFancy()采用纯粹表现功能的假设进行操作.

在Angular事件中包装全局浏览器事件的问题可以通过一些简单的运行阶段配置来处理:

angular.module('myApp')
    .run(function ($rootScope) {
        angular.element(window).on('resize', function () {
            $rootScope.$broadcast('global:resize');  
        })
    })
;
Run Code Online (Sandbox Code Playgroud)

现在,Angular不必完成与每个指令相关的所有工作$digest,但是您将获得相同的功能.

第二个问题是在n触发此事件时对元素数进行操作.同样,如果你不需要指令的所有细节和哨声,还有其他方法可以实现这一点.您可以在上面的运行块中扩展或调整方法:

angular.module('myApp')
    .run(function () {
        angular.element(window).on('resize', function () {
            var elements = document.querySelectorAll('.reacts-to-resize');
        })
    })
;
Run Code Online (Sandbox Code Playgroud)

如果确实有更复杂的逻辑需要在resize事件上发生,它仍然不一定意味着一个或多个指令是处理它的最佳方法.您可以使用实例化的简单中介服务,而不是前面提到的匿名运行阶段配置:

/**
 * you can inject any services you want: $rootScope if you still want to $broadcast (in)
 * which case, you'd have a "Publisher" instead of a "Mediator"), one or more services 
 * that maintain some domain objects that you want to manipulate, etc.
 */
function ResizeMediator($window) {
    function doSomethingFancy() {
        // whatever fancy stuff you want to do
    }

    angular.element($window).bind('resize', function () {
        // call doSomethingFancy() or maybe some other stuff
    });
}

angular.module('myApp')
    .service('resizeMediator', ResizeMediator)
    .run(resizeMediator)
;
Run Code Online (Sandbox Code Playgroud)

现在我们有一个封装的服务,可以进行单元测试,但不会运行未使用的执行阶段.

一些关注因素也会影响决策:

  • 死听者 - 使用选项1,您将为指令的每个实例创建至少一个事件监听器.如果这些元素被动态添加到DOM或从DOM中删除,并且您没有调用$on('$destroy'),那么当您的元素不再存在时,您将面临事件处理程序自我应用的风险.
  • 宽度/高度运算符的性能 - 我假设这里有盒模型逻辑,假设全局事件是浏览器调整大小.如果没有,请忽略这个; 如果是这样,你会要小心你正在访问哪些属性以及频率,因为浏览器重排可能是性能下降的罪魁祸首.

很可能这个答案并不像你希望的那样是"Angular",但这是我解决问题的方式,正如我所理解的那样,增加了仅使用盒子模型逻辑的假设.