Imagine the situation in AngularJS where you want to create a directive that needs to respond to a global event. In this case, let\'s say, the window resize event.
What
I have chosen another method, to effectively localise global events, like window resizing. It converts Javascript events to Angular scope events, via another directive.
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);
}
}
});
Which can be used, in the basic case, on the root element of the app
...
And then listen for the event in other directives
....
coded up as:
app.directive('myDirective', function() {
return {
link: function(scope, element) {
scope.$on('resize::resize', function() {
doSomethingFancy(element);
});
});
}
});
This has a number of benefits over other approaches:
Not brittle to the exact form on how directives are used. Your Option 2 requires my-directive
when angular treats the following as equivalent: my:directive
, data-my-directive
, x-my-directive
, my_directive
as can be seen in the guide for directives
You have a single place to affect exactly how the Javascript event is converted to the Angular event, which then affects all listeners. Say you later want to debounce the javascript resize
event, using the Lodash debounce function. You could amend the resize
directive to:
angular.element($window).on('resize', $window._.debounce(function() {
scope.$broadcast('resize::resize');
},500));
Because it doesn't necessarily fire the events on $rootScope
, you can restrict the events to only part of your app just by moving where you put the resize
directive
You can extend the directive with options, and use it differently in different parts of your app. Say you want different debounced versions in different parts:
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));
}
Used as:
You can later extend the directive with other events that might be useful. Maybe things like resize::heightIncrease
. resize::heightDecrease
, resize::widthIncrease
, resize::widthDecrease
. You then have one place in your app that deals with remembering and processing the exact dimensions of the window.
You can pass data along with the events. Say like the viewport height/width where you might need to deal with cross-browser issues (depending on how far back you need IE support, and whether you include another library to help you).
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
});
});
which gives you a single place to add to the data later. E.g. say you want to send the difference in dimensions since the last debounced event? You could probably add a bit of code to remember the old size and send the difference.
Essentially this design provides a way to convert, in a configurable manner, global Javascript events to local Angular events, and local not just to an app, but local to different parts of an app, depending on the placement of the directive.