$scope.$on Called several times - angularjs

I am using
$rootScope.$broadcast("closeviewMultiUserMainDetail");
and using in by below
$rootScope.$on("closeviewMultiUserMainDetail", function (event) {
//other code
});
sometimes ‍‍$rootScope.$on Called several times.
How can this be prevented?

$rootScope event listeners are not destroyed automatically and are always listening. That is why $rootScope.$on is getting called several times. You need to destroy the event listener using $destroy.
You can define your event like this:
var closeviewMultiUserMainDetail = $rootScope.$on('closeviewMultiUserMainDetail', function(event) { });
You can then destroy it like this:
$scope.$on('$destroy', function() {
closeviewMultiUserMainDetail(); });

Related

Angularjs $on called twice

So I have this situation where one controller is emitting the event and the other controller has the listener. Here is the code:
In controller A, I have this method:
$scope.process = function () {
var taskName = 'process';
$scope.$emit('process', taskName);
}
In controller B, I have this:
$rootScope.$on('process', function (event, taskName) {
//Do something here
});
Now whenever I visit other pages on application and comeback to this, the process listener gets created twice. I cannot use controller scope as the event is getting emitted from other controller. How can I destroy listener once it has completed its task? I have also tried $scope.$destroy(). Doesn't really work. What is the correct way of doing this?
I am on Angularjs 1.4.7.
Usually you do it in different way:
$rootScope.$broadcast(...)
...
$scope.$on(...)
Then you do not need to unsubscribe.
If you really need for some reason to subscribe to $rootScope, then:
var deregister = $scope.$on(...);
...
deregister(); // destory that listener

$scope outsite $on listener return undefined

I have this small piece of code that it is not working:
$scope.$on('play', function(e, data) {
$scope.tick = (data.time*100)/2.5;
});
console.log($scope.tick);
If I log $scope.tick outside $on return undefined. I can't understand why, I need to access that var outside the event listener.
This is the code triggering the $on
angular.module('videoCtrl', ['vjs.video'])
.controller('videoController', ['$scope', 'Timeline', function ($scope, Timeline) {
$scope.mediaToggle = {
sources: [
{
src: 'http://static.videogular.com/assets/videos/videogular.mp4',
type: 'video/mp4'
}
],
};
//listen for when the vjs-media object changes
$scope.$on('vjsVideoReady', function (e, videoData) {
videoData.player.on('timeupdate', function () {
var time = {
time: this.currentTime()
};
$scope.$broadcast('play', time);
})
});
}]);
and here the one receiving it:
angular.module('mediaTimelineCtrl', ['mt.media-timeline'])
.controller('DemoMediaTimelineController', function ($scope, Timeline) {
$scope.tick = 100;
$scope.disable = false;
$scope.timelines = Timeline.getTimelines();
$scope.$on('play', function(e, data) {
$scope.tick = (data.time*100)/2.5;
});
console.log($scope.tick);
});
Thanks to everyone
$on is a method provided by angularjs that allows you to listen for a specific broadcast at which point the provided function will be executed.
You log the variable right after you registered your callback that sets the tick variable. At this point your callback simply hasn't been called yet. For this to happen you must use $scope.$broadcast('play', data) somewhere in your code(Depending on wher you call $broadcast you might have to use $rootScope instead of $scope, because broadcast only sends the events to child scopes.(See angular docs here for more info)
Edit: This has been resolved in chat now. The callback provided in $on was called correctly, but the video library that is being used here called the event outside of an angular $digest cycle. Wrapping the assignment of $scope.tick into $scope.$apply([...]) did the trick.
$scope.$on use to listen to the events that fired by the $broadcast or an $emit. until one of those event fires, on function is not gonna fire and the content of the on function will not gonna execute.
But since the console log is outside of the on function it will execute whether scope.on fires or not. that is why conosole.log shows undefined.
if you put the console inside the scope.on function then it will execute only when the event fires
The value of $scope.tick is updated only when the event is fired. At the end of first digest cycle the value of $scope.tick is still undefined. You have to initialize the value of $scope.tick first or use $watch [$watch(watchExpression, listener, [objectEquality]);]. watchExp The expression being watched. It can be a function or a string, it is evaluated at every digest cycle. listener A callback, fired when the watch is first set, and then each time that during the digest cycle that a change for watchExp‘s value is detected. The initial call on setup is meant to store an initial value for the expression.

How to prevent $scope.$destroy?

I want to prevent $scope.$destroy event in my nested controller in my page. I see the event object in callback function has method preventDefault, but it's not working as I expect.
$scope.$on('$destroy', function(event) {
event.preventDefault(); // don't working!
});
It is possible to prevent $destroy event?
plnkr Example

AngularJS - Unbind $window of an event from specific directive

Inside one of my directives, I use angular.element($window).bind('scroll'). When the directive is destroyed, I then want to unbind it. Normally, I would just do:
$scope.$on('$destroy', function()
{
angular.element($window).unbind('scroll');
});
But what if another directive also has binded to the scroll event of the $window, and that event still needs to exist. If I use the unbind above, the other directive's binding is also eliminated.
What are my options?
Pass the same function reference to unbind/off as you pass to bind/on to unbind just that particular handler:
var fn = function () {};
angular.element($window).on('scroll', fn);
angular.element($window).off('scroll', fn);
For example:
var onScrollAction = function () {
// Do something
};
angular.element($window).on('scroll', onScrollAction);
scope.$on('$destroy', function () {
angular.element($window).off('scroll', onScrollAction);
});
Note that in jQuery the functions bind and unbind are deprecated. You can however still use them both with jQuery and jqLite as they just call on and off behind the scenes.
JQuery's on() supports namespacing events so they can be removed independent of each other. (https://api.jquery.com/on/)
Angular's jqLite however, does not. (https://docs.angularjs.org/api/ng/function/angular.element)
But if you include JQuery before Angular then JQuery will replace jqLite and you should be able to use namespaces. That's possibly not what you wanted to hear, but I'm not aware of any other way.
Just for more clearity on how it will goes in directive Within link function just use this make sure attach it to write element as in example it is attached to document
//attach event to document which is big thing
//make sure we remove it once we done with this
$document.on('keydown', handler);
//We need to remove this event as it should be only applicable when user is withing
//the scope of directive parent
scope.$on("$destroy", function () {
$document.off('keydown', handler);
});
//function gets executed when we hit the key specified from form
function handler (event) {
}
bind:
$window.onscroll = function() {
//your code
};
unbind:
$scope.$on('$destroy', function () {
$window.onscroll = undefined;
});

How to only listen once on $document from inside directive

I have an angular directive that needs to listen for click events on $document.
Let's call it clickDirective.
The click listener is added inside its link function.
The problem is that if there are multiple clickDirectives in the document, each one adds a new click listener on $document resulting in a document click handler function firing once for each clickDirective on the page. I only want it to fire once.
However, the callback function should be scoped inside the link function so that it has access to scope, element and attrs for example.
I tried adding it to the compile function and while the click handler only fires once, its callback handler doesn't have access to the goodies inside the link function.
How can that be achieved?
In the comments you say that you want to listen on $document for a click and use it to close a popover bubble. So I think it probably is correct to listen to the event from within the directive.
You can use one instead of on to bind the event only for one event (it'll unbind itself after the first click). So something like this:
module.directive('bubble', function ($document) {
return {
link: function (scope, element, attrs) {
element.on('click', function () {
scope.$apply(function(){
// Show the bubble here
$document.one('click', function () {
scope.$apply(function(){
// Hide the bubble here
});
});
});
});
}
};
});
If you really did want to listen only once on the $document you would use a service, but then you wouldn't have access to scope, element and attrs (because for which element would you expect them to be?). You could create a service that listens once then tracks the bubbles to be closed which would look something like this:
module.factory('bubbleCloser', function ($document, $rootScope) {
var toClose = [];
$document.on('click', function () {
$rootScope.$apply(function () {
toClose.forEach(function (element) {
element.hide();
});
toClose = [];
});
});
return {
addBubbleElement: function (element) {
toClose.push(element);
}
};
});
Which meets your original requirements of only listening to click once but is more complicated and doesn't give any benefits really (but maybe is useful if there's more to this that you haven't shown).

Resources