How do I bind to a custom event in an angularjs directive? - angularjs

I am having trouble catching an event which is sent like this:
document.body.dispatchEvent(event);
The directive below is on a div, inside the body:
.directive('myDirective',function(){
return {
restrict: 'A',
link: function(scope, element, attrs, pageCtrl){
element.bind('myEvent', function(){
console.log('caught my event!');
});
}
};
});
This works if the event is sent with triggerHandler on the div.
event = new Event('myEvent');
elem.triggerHandler(event);
How can I catch a custom event on the document body inside a directive?
Update:
This is for use in a Cordova app, with an existing plugin, which dispatches events on document.body. So is there another way I can catch those events in a directive on a div?

The problem is that events bubble, meaning when you dispatch the event from document.body, it propagates up through its ancestors. Since it doesn't travel down through its descendants, your directive's event listener is never fired.
You can, however, catch the event on body and fire to your div using Angular's internal event system.
Create two directives
One to decorate the body element with the following behavior:
catches the Cordova event, then...
broadcasts an Angular-internal event from rootScope
.directive('bodyDirective',function($rootScope){
return {
restrict: 'A',
link: function(scope, element, attrs){
element.bind('myEvent', function(){
$rootScope.$broadcast('cordovaEvent');
});
}
};
})
... and another to decorate the div with behavior that will catch the Angular event:
.directive('divDirective', function(){
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$on('cordovaEvent', function(){
// do something here
});
}
}
})
Plunker (you must view the preview in a separate window and watch console there)

Related

Angular remove class on location change

I want to remove a class when ever the user changes page, mostly because I want to collapse the bootstrap navbar. To do this I have created a directive that will remove a specified class. Whats the best way to trigger my directive on the $routeChangeSuccess event without listening to to the event within the directive as I would like to keep it flexible.
app.directive('removeClass', function() {
return {
scope: {},
restrict: 'A',
link: function(scope, element, attrs) {
//someway to trigger this method e.g. from an event listener outside the directive
scope.removeClass = function() {
element.removeClass(attrs.removeClass);
}
}
};
});
You can change a $rootScope variable with ng-class. Set that variable when there in a controller depending on page.

AngularJS use a directive to prevent other directives to execute

Some actions in my Angular app require the user to be registered. If the user is not registered we want to show a "Register modal" and prevent the original action.
Those actions can be triggered via ng-click or any other "click binding" directive (for example the 'modal-toggle' one).
So I found this solution: https://stackoverflow.com/a/16211108/2719044
This is pretty cool but only works with ng-click.
I first wanted to make the "terminal" property of the directive dynamic but couldn't manage to do it.
So the idea was to set "terminal" to true and manually prevent default click action in the directive.
Here is my DOM
<!-- This can work with terminal:true and scope.$eval(attrs.ngClick) (see example above) -->
<div user-needed ng-click="myAction()">Do it !</div>
<!-- This doesn't work. I can't manage to prevent the modal-toggle to be executed -->
<div user-needed modal-toggle="my-modal-id-yey">Show yourself modal !</div>
And my directive(s) (which don't work...)
// First try (with terminal:true)
app.directive('userNeeded', function() {
return {
priority: -100,
terminal: true,
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('click', function(e) {
if(isRegistered()) {
// Here we do the action like scope.$eval or something
}
});
}
};
});
// Second try (with stopPropagation)
app.directive('userNeeded', function() {
return {
priority: -100
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('click', function(e) {
if(!isRegistered()) {
e.stopPropagation();
}
});
}
};
});
...And that's why I'm here. Any idea ?
Thanks a lot.
You were extremely close. Instead of stopPropagation you needed stopImmediatePropagation. The difference between the two is summarized in this StackOverflow answer by #Dave:
stopPropagation will prevent any parent handlers from being
executed while stopImmediatePropagation will do the same but
also prevent other handlers from executing.
So to fix the code, all we have to do is swap out that method and VoilĂ :
app.directive('userNeeded', function() {
return {
priority: -100
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('click', function(e) {
if(!isRegistered()) {
e.stopImmediatePropagation();
}
});
}
};
});
Here is an example Plunker of the working code. In the example I modified the directive slightly to allow specific events to be specified (such as user-needed="submit") by passing the value directly to the element.bind function; however, it defaults to 'click'.

Angular Directive for keydown breaks other functionality

I'm trying to listen to keydown events on a formfield. To do this I've built a directive with a "keydown" attribute as follows:
var keydown = function() {
return {
restrict: 'A',
scope: {
keydown: '=keydown'
},
link: function(scope, elem, attr) {
$(elem).keydown(function(evt){
scope.keydown(evt);
});
}
};
};
Now I add this to my inputs markup:
<input class="search-input" type="text" ng-model="queryStr" ng-change="redrawUI()" keydown="processSearchBox" >
Now my keydown function is called and that works fine... however now my model is no longer bound to the input form field... if I change the model the form doesn't update
The problem is you are creating a new "isolate" scope which is detached from the scope where redrawUI() lives. Take a look at this fiddle for an alternate approach where you inherit from the parent scope and use scope.$eval to process your event.

Directive-to-directive communication in AngularJS?

I already know that you can set up a controller within a directive, and that other directives can call the functions on that controller. Here's what my current directive looks like:
app.directive("foobar", function() {
return {
restrict: "A",
controller: function($scope) {
$scope.trigger = function() {
// do stuff
};
},
link: function(scope, element) {
// do more stuff
}
};
});
I know that I could call it like this:
app.directive("bazqux", function() {
return {
restrict: "A",
require: "foobar",
link: function(scope, element, attrs, fooBarCtrl) {
fooBarCtrl.trigger();
}
};
});
However, I want to be able to call trigger from any directive, not just my own custom ones, like this:
<button ng-click="foobar.trigger()">Click me!</button>
If that doesn't work, is there a way to bring in a third directive to make it happen? Like this?
<button ng-click="trigger()" target-directive="foobar">Click me!</button>
Thanks!
Sounds like you need an angular service. http://docs.angularjs.org/guide/dev_guide.services
This will allow you to share functionality across directives.
Here's a similar question: Sharing data between directives
One simple way of accomplishing application-wide communication between any components would be to use global events (emitted from the $rootScope). For example:
JS:
app.directive('directiveA', function($rootScope)
{
return function(scope, element, attrs)
{
// You can attach event listeners in any place (controllers, too)
$rootScope.$on('someEvent', function()
{
alert('Directive responds to a global event');
});
};
});
HTML:
<button ng-click="$emit('someEvent')">Click me!</button>
Here you're emitting an event from the child scope but it will eventually reach the $rootScope and run the previous listener.
Here's a live example: http://plnkr.co/edit/CpKtR5R357tEP32loJuG?p=preview
When talking on irc it turned out that the communication is unnecessary:
I've got an attribute-restricted directive which performs some DOM manipulation on its parent element when it's "triggered"
A solution is to keep the logic inside the same directive and just to apply the dom changes to the parent.
http://jsfiddle.net/wt2dD/5/
scope.triggerSmthOnParent = function () {
element.parent().toggleClass('whatewer');
}

How to register my own event listeners in AngularJS?

How do I register my own event listeners in an AngularJS app?
To be specific, I am trying to register Drag and Drop (DND) listeners so that when something is dragged and dropped in a new location of my view, AngularJS recalculates the business logic and updates the model and then the view.
Adding an event listener would be done in the linking method of a directive. Below I've written some examples of basic directives. HOWEVER, if you wanted to use jquery-ui's .draggable() and .droppable(), what you can do is know that the elem param in the link function of each directive below is actually a jQuery object. So you could call elem.draggable() and do what you're going to do there.
Here's an example of binding dragstart in Angular with a directive:
app.directive('draggableThing', function(){
return {
restrict: 'A', //attribute only
link: function(scope, elem, attr, ctrl) {
elem.bind('dragstart', function(e) {
//do something here.
});
}
};
});
Here's how you'd use that.
<div draggable-thing>This is draggable.</div>
An example of binding drop to a div or something with Angular.
app.directive('droppableArea', function() {
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
elem.bind('drop', function(e) {
/* do something here */
});
}
};
});
Here's how you'd use that.
<div droppable-area>Drop stuff here</div>
I hope that helps.
Hiding event handling and dom manipulation in a directive is pretty much the the angularjs way. Calling scope.$apply when an event fires tells angular to update the view.
You might consider using jquery-ui like in this sample (see angular wiki of examples
I work with the angular-ui group and there is a simple event wrapper you might find useful.
Nice solution by Ben but keep in mind you will need to access originalEvent and original element. According to Mozilla documentation two conditions must meet https://developer.mozilla.org/en-US/docs/DragDrop/Drag_Operations
draggable is true
Listener for dragstart
So directive might look something like this
app.directive('draggableThing', function () {
return function(scope, element, attr) {
var pureJsElement = element[0];
pureJsElement.draggable = true;
element.bind('dragstart', function(event) {
event.originalEvent.dataTransfer.setData('text/plain',
'This text may be dragged');
//do something here.
});
}
});
A good step by step example is available here http://blog.parkji.co.uk/2013/08/11/native-drag-and-drop-in-angularjs.html

Resources