簡體   English   中英

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

[英]AngularJS : What is the best way to bind to a global event in a directive

想象一下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);
             });
        }
    };
});

選項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);
});

這兩種方法都運行正常,但我覺得選項二並不是'Angular-ish'。

有任何想法嗎?

我選擇了另一種方法來有效地本地化全局事件,比如窗口大小調整。 它通過另一個指令將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);
    }
  }
});

在基本情況下,可以在應用程序的根元素上使用哪個

<body ng-app="myApp" resize>...

然后在其他指令中監聽事件

<div my-directive>....

編碼為:

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

與其他方法相比,這有許多好處:

  • 對於如何使用指令的確切形式並不脆弱。 當angular將以下內容視為等效時,你的選項2需要my-directivemy:directivedata-my-directivex-my-directivemy_directive ,可以在指令指南中看到

  • 您只有一個地方可以准確地影響Javascript事件如何轉換為Angular事件,然后影響所有偵聽器。 假設你后來想要使用Lodash去抖功能去抖動javascript resize事件。 您可以將resize指令修改為:

     angular.element($window).on('resize', $window._.debounce(function() { scope.$broadcast('resize::resize'); },500)); 
  • 因為它不一定會在$rootScope上觸發事件,所以只需移動resize指令的位置即可將事件限制為僅部分應用程序

     <body ng-app="myApp"> <div> <!-- No 'resize' events here --> </div> <div resize> <!-- 'resize' events are $broadcast here --> </div> 
  • 您可以使用選項擴展指令,並在應用的不同部分以不同方式使用它。 假設您需要不同部分的不同去抖版本:

     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)); } 

    用作:

     <div resize> <!-- Undebounced 'resize' Angular events here --> </div> <div resize="500"> <!-- 'resize' is debounced by 500 milliseconds --> </div> 
  • 您可以稍后使用可能有用的其他事件擴展該指令。 也許像resize::heightIncrease這樣的東西。 resize::heightDecreaseresize::widthIncreaseresize::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 }); }); 

    這為您提供了一個稍后添加到數據的位置。 例如,假設您要發送自上次去抖事件以來尺寸的差異? 您可以添加一些代碼來記住舊的大小並發送差異。

本質上,這種設計提供了一種方法,可以以可配置的方式將全局Javascript事件轉換為本地Angular事件,而不僅僅局部轉換為應用程序,而是本地應用程序的不同部分,具體取決於指令的位置。

在框架之上進行開發時,我經常發現在設計一個慣用之前,對於一個問題進行厭惡思考是有幫助的。 回答“什么”和“為什么”驅逐“如何”。

這里的答案實際上取決於doSomethingFancy()的復雜性。 是否存在與此指令實例關聯的數據,一組功能或域對象? 這是一個純粹的表現性問題,比如將某些元素的widthheight屬性調整到適當比例的窗口大小? 確保你正在使用合適的工具; 當工作需要鑷子並且您可以使用獨立配對時,不要攜帶整個瑞士軍刀。 為了繼續這種方式,我將假設doSomethingFancy()是一個純粹的表現函數。

在Angular事件中包裝全局瀏覽器事件的問題可以通過一些簡單的運行階段配置來處理:

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

現在,Angular不必在每個$digest上執行與指令相關的所有工作,但是您將獲得相同的功能。

第二個問題是在觸發此事件時對n個元素進行操作。 同樣,如果你不需要指令的所有細節和哨聲,還有其他方法可以實現這一點。 您可以在上面的運行塊中擴展或調整方法:

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

如果確實有更復雜的邏輯需要在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)
;

現在我們有一個封裝的服務,可以進行單元測試,但不會運行未使用的執行階段。

一些關注因素也會影響決策:

  • 死聽者 - 使用選項1,您將為指令的每個實例創建至少一個事件監聽器。 如果這些元素被動態添加到DOM或從DOM中刪除,並且您沒有調用$on('$destroy') ,那么當您的元素不再存在時,您將面臨事件處理程序自我應用的風險。
  • 寬度/高度運算符的性能 - 我假設這里有盒模型邏輯,假設全局事件是瀏覽器調整大小。 如果沒有,請忽略這個; 如果是這樣,你會要小心你正在訪問哪些屬性以及頻率,因為瀏覽器重排可能是性能下降的罪魁禍首

很可能這個答案並不像你希望的那樣是“Angular”,但這是我解決問題的方式,正如我所理解的那樣,增加了僅使用盒子模型邏輯的假設。

在我看來,我會使用方法#1和使用$ window服務進行一些調整。

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

     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.
             anguar.element($window).bind('resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});

#2參考這種方法並且思路略有改變 - 你可以把這個事件監聽器放在更高的位置,比如說app.run - 這時當事件發生時你可以播放另一個指令選擇的事件並做一些花哨的事情當那件事發生時。

編輯 :我對這種方法的思考越多,我實際上就越喜歡它的第一個...非常有力的方式來監聽窗口調整大小事件 - 可能在將來其他東西需要“知道”此信息除非你做這樣的事情,否則你不得不設置 - 再次 - window.resize事件的另一個事件監聽器。

app.run

app.run(function($window, $rootScope) {
  angular.element($window).bind('resize', function(){
     $rootScope.$broadcast('window-resize');
  });
}

Directive angular.module('app')。directive('myDirective',function($ rootScope){

     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.
             $rootScope.$on('window-resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});

最后一個如何做的事情的一個很棒的來源是跟隨angular-ui家伙,例如ui-bootstrap 我從這些家伙那里學到了很多東西,例如學習角度單元測試的樂趣。 它們提供了一個非常干凈的代碼庫來結賬。

第二種方法感覺更脆弱,因為Angular提供了很多方法來引用模板中的指令( my-directivemy_directivemy:directivex-my-directivedata-my-directive等),所以CSS選擇器覆蓋它們可能會變得非常復雜。

如果你只在內部使用指令或者它們只包含一個單詞,那么這可能不是什么大問題。 但是,如果其他開發人員(具有不同的編碼約定)可能正在使用您的指令,您可能希望避免使用第二種方法。

但我會務實。 如果您正在處理少數幾個實例,請使用#1。 如果你有數百個,我會選擇#2。

這是你可以做到的一種方式,只需將元素存儲在一個數組中,然后在“全局事件”中,您可以循環遍歷元素並執行您需要執行的操作。

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

    var elements = [];

    $window.on('resize', function(){
       elements.forEach(function(element){
           // In here we have our operations on the element
       });
    });

    return {
        link: function(scope, element){
            elements.push(element);
        }
    };
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM