Programing

AngularJS : 지시문에서 전역 이벤트에 바인딩하는 가장 좋은 방법은 무엇입니까?

lottogame 2020. 11. 30. 07:42
반응형

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

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

두 접근법 모두 잘 작동하지만 두 번째 옵션은 실제로 '각도'가 아니라고 생각합니다.

어떤 아이디어?


창 크기 조정과 같은 전역 이벤트를 효과적으로 지역화하기 위해 다른 방법을 선택했습니다. 다른 지시문을 통해 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);
      });
    });
  }
});

이는 다른 접근 방식에 비해 많은 이점이 있습니다.

  • 지시문이 사용되는 방식에 대한 정확한 형식으로 깨지지 않습니다. 귀하의 옵션 2가 필요합니다 my-directive: 각 치료가 동등하게 다음과 같은 경우 my:directive, data-my-directive, x-my-directive, my_directive과에서 볼 수 있습니다 지침에 대한 가이드

  • Javascript 이벤트가 Angular 이벤트로 변환되는 방식에 정확히 영향을 미치고 모든 리스너에 영향을 미치는 단일 위치가 있습니다. 나중에 Lodash 디 바운스 함수를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::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
      });
    });
    

    나중에 데이터에 추가 할 수있는 단일 위치를 제공합니다. 예를 들어 마지막 디 바운스 이벤트 이후 차원의 차이를 보내고 싶다고하나요? 이전 크기를 기억하고 차이를 보내기 위해 약간의 코드를 추가 할 수 있습니다.

기본적으로이 디자인은 구성 가능한 방식으로 전역 Javascript 이벤트를 로컬 Angular 이벤트로, 로컬을 앱뿐만 아니라 앱의 다른 부분으로 로컬로 변환하는 방법을 지시문의 배치에 따라 제공합니다.


프레임 워크 위에서 개발할 때, 관용구를 디자인하기 전에 문제에 대해 불가지론 적으로 생각하는 것이 도움이되는 경우가 많습니다. "무엇"과 "왜"에 답하면 "어떻게"가 나옵니다.

여기서 답은 실제로 doSomethingFancy(). 이 지시문의 인스턴스와 관련된 데이터, 기능 집합 또는 도메인 개체가 있습니까? 특정 요소 width또는 height속성을 적절한 비율의 창 크기로 조정하는 것과 같은 순전히 표현적인 문제 입니까? 작업에 적합한 도구를 사용하고 있는지 확인하십시오. 작업에 핀셋이 필요하고 독립형 쌍을 사용할 수있을 때 전체 스위스 군용 칼을 가져 오지 마십시오. 이 맥락에서 계속하기 위해, 저는 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)
;

Now we have an encapsulated service that can be unit tested, but doesn't run unused execution phases.

A couple concerns that would also factor into the decision:

  • Dead listeners - with Option 1, you're creating at least one event listener for every instance of the directive. If these elements are being dynamically added to or removed from the DOM, and you don't call $on('$destroy'), you're running the risk of event handlers applying themselves when their elements no longer exist.
  • Performance of width/height operators - I'm assuming that there is box-model logic here, given that the global event is the browser resize. If not, ignore this one; if so, you'll want to be careful about which properties you're accessing and how often, because browser reflows can be a huge culprit in performance degradation.

It's likely that this answer is not as "Angular" as you were hoping for, but it's the way I'd solve the problem as I understand it with the added assumption of box-model-only logic.


In my opinion I would go with method #1 and a little tweak of using the $window service.

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 In reference to this approach and a slight change in thinking here - you could put this event listener somewhere higher up in say app.run - and here when the event happens you can broadcast another event which the directive picks up and does something fancy when that event takes place.

EDIT: The more I think about this method the more I actually start to like it over the first one... Great robust way to listen to the window resize event - maybe in the future something else needs to "know" this info as well and unless you do something like this you are forced to setup - yet again - another event listener to the window.resize event.

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

Finally An awesome source of how to do stuff is to follow the angular-ui guys for example the ui-bootstrap. I have learned a bunch of how to stuff from these guys for example the joys of learning to unit test in angular. They provide a great clean codebase to checkout.


The second approach feels more brittle, since Angular offers many ways to refer to the directive in the template (my-directive, my_directive, my:directive, x-my-directive, data-my-directive, etc.) so a CSS selector covering them all might get really complex.

This probably isn't a big deal if you only use the directives internally or they consist of a single word. But if other developers (with different coding conventions) might be using your directives, you may want to avoid the second approach.

But I'd be pragmatic. If you're dealing with a handful of instances, go with #1. If you have hundreds of them, I'd go with #2.


Here's one way you could do it, just store your elements in an array, then in the "global event" you can loop through the elements and do what you need to do.

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

참고URL : https://stackoverflow.com/questions/23272169/angularjs-what-is-the-best-way-to-bind-to-a-global-event-in-a-directive

반응형