Directive handling event from model - angularjs

I have a CurrentUser model class that, when the user is not authenticated, I want to throw a 'NOT_AUTHENTICATED' event.
On certain pages, I want to use a directive that will handle this event and display a modal.
Will emiting events from a model get bubble up to the directive link scope?

Angular events are triggered by and received by scopes. So, you need a $scope object to broadcast an event and you need a scope object to listen to an event. If by "model" you mean an angular service, then you can inject the $rootScope and $broadcast the event from there like this...
myApp.factory('theModel', function($rootScope) {
$rootScope.$broadcast('NOT_AUTHENTICATED');
});
The event will bubble down the scope chain and can be heard by your directive's scope in a link function..
myApp.directive('theDirective', function () {
return {
...
link:function(scope, element, attrs) {
scope.$on('NOT_AUTHENTICATED', function (event) {
...
}
}
};
});

Related

How can I convert custom jquery event listener into angularjs listener?

I have created angularjs component. I wrote a custom event listener in jquery, event is triggered by non-angularjs library.
$( "#myDiv" ).on( "CornerstoneImageRendered", function(e) {
// buisness logic
});
myDiv is a div which is part of angularjs component.
I want to write this listener into angularjs component. How can I do it?
PS: Event link https://github.com/cornerstonejs/cornerstone/wiki/CornerstoneImageRendered-Event
Create a custom directive:
app.directive("myEventListener", function() {
return {
link: postLink
};
function postLink(scope, elem, attrs) {
elem.on( "CornerstoneImageRendered", function(event) {
// business logic
scope.$eval(attrs.myEventListener, {$event: event});
scope.$apply();
});
}
});
Usage
<div id="myDiv" my-event-listener="onMyEvent($event)">
</div>
JS
$scope.onMyEvent = function(event) {
console.log(event);
};
For more information, see AngularJS Developer Guide - Creating Custom Directives
If you're going to listen on events from an external, non angularjs, library you need to notify angularjs when a change has happend.
One way to do this is by wrapping your logic, which is triggered by the external event, in the $scope.$apply method. The $apply method will
notify angularjs that a change has happend and triggered the digest loop which will sync change from the scope to the view.
Add the event inside the controller and try it like this
$("#myDiv").on("CornerstoneImageRendered", function(e) {
$scope.$apply(function () {
// buisness logic on the scope
});
});
Here's an great artile on the subject
To archive what you want first you need use
angular.element and then you can add event listener

Why isn't $destroy triggered when I call element.remove?

I can't figure out why the $destroy event is not triggered in the following example. Can someone explain why it is not triggered, and in what scenarios it will be triggered?
Here's the plunkr: http://plnkr.co/edit/3Fz50aNeuculWKJ22iAX?p=preview
JS
angular.module('testMod', [])
.controller('testCtrl', function($scope){
$scope.removeElem = function(id) {
var elem = document.getElementById(id);
angular.element(elem).remove();
}
}).directive('testDir',[function() {
return {
scope:true,
link: function(scope) {
console.log('in directive');
scope.$on('$destroy', function(){
alert('destroyed');
})
}
}
}]);
HTML
<body ng-controller='testCtrl'>
<div testDir id='test'>I will be removed.</div>
<button ng-click='removeElem('test')'>remove</button>
</body>
The problem is your listening for the $destroy event on the scope, but $destroy is being triggered on the element.
From angular.js source (I'm sure it's documentated on the website somewhere, but I didn't look):
$destroy - AngularJS intercepts all jqLite/jQuery's DOM destruction
apis and fires this event on all DOM nodes being removed. This can
be used to clean up any 3rd party bindings to the DOM element before
it is removed.
Your directive should be (note that I added scope,element, and attrs as link arguments): Also, here is a plunker.
directive('testDir',[function() {
return {
scope:true,
link: function(scope,element,attrs) {
console.log('in directive');
element.on('$destroy', function(){
alert('destroyed');
})
}
};
}]);
I am puzzled on why the $destroy event is not triggered on remove() method.
As per the docs, the $destroy event is triggered in two cases.
Just before a scope is destroyed
Just before an element is removed from the DOM
The purpose being "cleanup". You can listen on the $destroy event and perform necessary cleanups before letting a scope or element be destroyed. ngIf, ngSwitch, ngRepeat and other in-built directives/methods use the $destroy event to perform cleanups.
A best example would be the ngRepeat directive
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js
On line 339 you can notice the $destroy event being triggered. You can listen on the event and perform any action just before an item is removed from the list used by ngRepeat.
ngRepeat $destroy Example Plunk -- http://goo.gl/mkozCY

Angular custom directive: Access parent scope property in linking function

I want to set the name of an event (click or touch) on a parent scope in angular as a string. On a child scope I want to use that string to bind an event with element.on.
Example:
angular.module('directives', [])
.directive('Sidebar', function () {
'use strict';
return {
link: function (scope) {
//determines whether or not to use click or touch events
//I want to use this to bind events with
//element.on(scope.sidebarClickEvent) in child scopes
scope.sidebarClickEvent = 'click';
},
restrict: 'E',
templateUrl: 'sidebar.html'
};
})
.directive('collapseBtn', function() {
'use strict';
return function (scope, element) {
element.on(scope.sidebarClickEvent /* undefined */ , function () {
//scope.sidebarClickEvent is available here, when the event handler executes
scope.toggleSidebarCollapseState();
element.find('i').toggleClass('icon-double-angle-right');
});
};
})
The problem is that properties defined on the parent scope aren't available when I bind the events, so scope.sidebarClickEvent is undefined when I bind the event. But if I change it to a regular click event then I can get the property in the event handler.
Can I access properties inherited by the scope at the time that the event binding occurs? I'm not even sure that I'm understanding scope inheritance properly here, so pointing out errors in my understanding would also be appreciated.
Thanks.

Angular - receiving info from controller inside directive

I have a directive which is responsible to render audio and video items on a page:
myApp.directive('videoitem', function($compile){
return {
replace: true,
templateUrl: '/templates/directives/videoitem.html',
scope: {
videoChunkItem: "=",
videostartedCallback: "&videostarted",
videoendedCallback: "&videoended",
},
link: function($scope, $element, $attributes){
var startVideo = function(){
$element[0].load();
$element[0].play();
$scope.videostartedCallback();
};
$element.bind("ended", function(){
$scope.videoendedCallback();
$scope.$apply();
});
/**
* Here I am using on click event to start the video
* but the real case is that the trigger for video playing should come from controller.
* Any idea how to do it?
*/
$element.on('click', function(){
startVideo();
});
}
};
});
Is it possible from a route controller to send some event or something else to communicate from controller to directive to call startVideo method?
The flow of communication is from route controller to directive ... When in a controller occure some event I want to invoke directive's startVideo method.
Without knowing your exact use-case, my approach would be to define a new scope property, e.g. play : "=" (to set it to stop when the video ends), and $observe attrs.play or $scope.$watch "play" it. In your template you bind it to a $scope variable that is triggered by the controller.
<video-item play="videoControls.play"></video-item>
Here's a plunker where you use two-way binding, note how you don't have to do anything:
http://plnkr.co/edit/3OI801KcvYVoySDvcm8i?p=preview
If you want to allow expressions, you have to observe the attribute to get the interpolated value:
http://plnkr.co/edit/gi6FSPPlN599CtDjKBOb?p=preview

Creating a scope-independent fadein/fadeout directive in AngularJS

To set the stage - this is not happening within a single scope, where I can bind a simple attribute. The element I want to fade in/out does not sit inside a controller, it sits inside the ng-app (rootScope). Further, the button that's clicked is in a child scope about 3 children deep from root.
Here is how I'm currently solving this:
HTML (sitting in root scope):
<ul class="nav-secondary actions"
darthFader fadeDuration="200"
fadeEvent="darthFader:secondaryNav">
Where darthFader is my directive.
Directive:
directive('darthFader',
function() {
return {
restrict: 'A',
link: function($scope, element, attrs) {
$scope.$on(attrs.fadeevent, function(event,options) {
$(element)["fade" + options.fade || "In"](attrs.fadeduration || 200);
});
}
}
})
So here I'm creating an event handler, specific to a given element, that is calling fadeIn or fadeOut, depending on an option being passed through the event bus (or defaulting to fadeIn/200ms).
I am then broadcasting an event from $rootScope to trigger this event:
$rootScope.$broadcast('darthFader:secondaryNav', { fade: "Out"});
While this works, I'm not crazy about creating an event listener for every instance of this directive (while I don't anticipate having too many darthFader's on a screen, it's more for the pattern I would establish). I'm also not crazy about coupling my attribute in my view with an event handler in both my controller & directive, but I don't currently have a controller wrapping the secondary-nav, so I'd have to bind the secondaryNav to $rootScope, which I don't love either. So my questions:
Is there a way to do this without creating an event handler every time I instantiate my directive? (maybe a service to store a stateful list of elements?)
How should I decouple my view, controller & directive?
Any other obvious questions I'm missing?
Cheers!
You mention in your question
The element I want to fade in/out does not sit inside a controller, it sits inside the ng-app (rootScope).
I believe if I were to write this same functionality, I would put the element in its own controller--controllers are responsible for managing the intersection of the view and the model, which is exactly what you're trying to do.
myApp.controller('NavController', function($scope) {
$scope.fadedIn = false;
});
<ul ng-controller="NavController"
class="nav-secondary actions"
darthFader fadeDuration="200"
fadeShown="fadedIn">
myApp.directive('darthFader', function() {
return {
restrict: 'A',
link: function($scope, element, attrs) {
var duration = attrs.fadeDuration || 200;
$scope.$watch(attrs.fadeShown, function(value) {
if (value)
$(element).fadeIn(duration);
else
$(element).fadeOut(duration);
});
}
};
});
If you're worried about sharing the fade in/out state between multiple controllers, you should create a service to share this state. (You could also use $rootScope and event handlers, but I generally find shared services easier to debug and test.)
myApp.value('NavigationState', {
shown: false
});
myApp.controller('NavController', function($scope, NavigationState) {
$scope.nav = NavigationState;
});
myApp.controller('OtherController', function($scope, NavigationState) {
$scope.showNav = function() {
NavigationState.shown = true;
};
$scope.hideNav = function() {
NavigationState.shown = false;
};
});
<ul ng-controller="NavController"
class="nav-secondary actions"
darthFader fadeDuration="200"
fadeShown="nav.shown">
<!-- ..... -->
<div ng-controller="OtherController">
<button ng-click="showNav()">Show Nav</button>
<button ng-click="hideNav()">Hide Nav</button>
</div>
Create a custom service, inject it in the controller. Call a method on that service that will do the fade-in/fade-out etc. Pass a parameter to convey additional information.

Resources