Why is rootScope.on called multiple times in my application? - angularjs

In my AngularJS application, I have a controller-A and a factory. I am using the following code in factory to call the function in controller-A. In the initial call, the function in controller A's function executes 1 time; on the next call the controller-A's function executes 2 times. Hence the number of times executed get increased for each call. Is it possible to avoid this, please advise me. I have added the factory code and controller-A code below:
Factory code:
updateUserData: function (value, action) {
$("#myModalInsertUser").modal('hide');
var id = value.Id;
var params = {};
params.id = depotId;
$rootScope.selectedId = params;
$rootScope.$emit("EVENT_1", {id});
});
Controller-A code:
var listener = $rootScope.$on("EVENT_1", function(event, params, reload) {
$scope.confirmUserInfo(params);
});
$scope.confirmUserInfo = function(params) {
$('#myModalConfirmUser').modal('show');
$('#closeConfirmUser').unbind('click').click(function () {
$('#myModalConfirmUser').modal('hide');
var params = $rootScope.selectedId;
$scope.getUsers(params);
$scope.$on('$destroy', listener);
});
}

Attach the event listener to $scope and it will be automatically destroyed when the scope is destroyed:
̶v̶a̶r̶ ̶l̶i̶s̶t̶e̶n̶e̶r̶ ̶=̶ ̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶.̶$̶o̶n̶(̶"̶E̶V̶E̶N̶T̶_̶1̶"̶,̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶(̶e̶v̶e̶n̶t̶,̶ ̶p̶a̶r̶a̶m̶s̶,̶ ̶r̶e̶l̶o̶a̶d̶)̶ ̶{̶
var deregisterFn = $scope.$on("EVENT_1", function(event, params, reload) {
$scope.confirmUserInfo(params);
});
$scope.confirmUserInfo = function(params) {
$('#myModalConfirmUser').modal('show');
$('#closeConfirmUser').unbind('click').click(function () {
$('#myModalConfirmUser').modal('hide');
var params = $rootScope.selectedId;
$scope.getUsers(params);
̶$̶s̶c̶o̶p̶e̶.̶$̶o̶n̶(̶'̶$̶d̶e̶s̶t̶r̶o̶y̶'̶,̶ ̶l̶i̶s̶t̶e̶n̶e̶r̶)̶;̶
});
}
The recommended practice is to broadcast events from $rootScope and receive them on the $scope interested in the event.
is it possible to destroy the listener before the scope gets destroyed?
To remove the listener, simply invoke the de-register function:
deregisterFn();

Related

How to call a function from $rootScope

I need to call an Angular function on the hidden event of a bootstrap modal.
Here is my hidden event handler:
$('#modalAddAction').on('hidden.bs.modal',
function (e) {
console.log("in hidden.bs.modal");
var $rootScope = angular.element(document.querySelector("[ng-controller=actionsController]")).scope();
if ($rootScope) {
$rootScope.$apply(function () {
$rootScope.initializeAt1();
});
}
});
Here is the function defined in the controller I need to call:
$scope.initializeAt1 = function () {
$scope.data.addAction.actionTypeId; // initialized in $scope.getActionTypes
$scope.data.addAction.actionStatus = 1;
// initializers help set drop downs to appropriate selection.
$scope.data.addAction.actionType1.actionRecommendedByLerId = 0;
$scope.data.addAction.actionType1.actionsRecommendedByLer = [];
$scope.data.addAction.actionType1.actionProposedBySupervisorId = 0;
$scope.data.addAction.actionType1.actionTakenBySupervisorId = 0;
$scope.data.addAction.actionType1.actionChargeId = 0; // formerly initialized in $scope.getActionCharges
$scope.data.addAction.actionType1.actionCharges = [];
}();
So the first time the controller factory is run, the initializeAt1 function calls itself and does the initialization I need.
Then I try to call initializeAt1 again whenever the modal is hidden, whether from a save buttion, a cancel button, or just clicking on the screen.
The #modalAddAction - hidden event here gives me this error:
[$rootScope:inprog] $digest already in progress
So now I try to change he event handler to this (comment out the apply and just directly call the function from $rootScope):
$('#modalAddAction').on('hidden.bs.modal',
function (e) {
console.log("in hidden.bs.modal");
var $rootScope = angular.element(document.querySelector("[ng-controller=actionsController]")).scope();
//if ($rootScope) {
//$rootScope.$apply(function () {
$rootScope.initializeAt1();
//});
//}
});
And now I get this error.
$rootScope.initializeAt1 is not a function
So I have the rootScope, but why does it not see the function?
I figured it out.
It just didn't like the function being self called after the definition.
When I inspected the $scope off of the $rootScope initailizeAt1 was listed as undefined.
Had to take () off of the end.
And add the call:
$scope.initializeActions = function () {
$scope.initializeAt1();
}
$scope.initializeActions();
at the end.
Will then add init for At2, At3 and so on to initializeActions.

How to synch controller creation and emitting events?

I am interested if any of you run into this issue and how you solved it. I have a page that has it's controller and in that page, each tab has it's own controller. Depending on how I access the page, via navigation or direct url link, I have issue that tab controllers are not yet ready, by the time outer controller raises event. My solution was to raise "ControllerCreated" event, passing instance of the controller as the event arg. from each of the tab controllers. Outer controller listens for these and invokes on them "load" operation. Is there other built in way to synch this without having to raise custom event?
Event Recorder Service
One approach is to store outer events in a service and have child controllers use the service to "catch-up" and subscribe to the events.
app.service("eventRecorder", function() {
var lastEvent;
var subscriberIdNum = 0;
var subscriberList = {};
this.record = function evRecorder (eventValue) {
lastEvent = eventValue;
};
this.subscribe = function evSubscribe(callbackFn) {
//callback for subscriber to "catch-up"
if (lastEvent) { callbackFn(lastEvent) };
//save subscriber
subscriberIdNum++
var subscriberIdString = 'id'+subscriberIdNum;
subscriberList[subscriberIdString] = callbackFn;
//return unSubscribe function
return function unSubscribe() {
delete subscriberList[subscriberIdString];
};
};
this.notify = function evNotify( eventValue ) {
this.record(eventValue);
angular.forEach(subscriberList, function (cb) {
cb(eventValue);
});
};
});
Client Controller Example
app.controller("client", function($scope, eventRecorder) {
var vm = $scope;
var unSubscribe = eventRecorder.subscribe(function(value) {
//callback executes immediately to "catch-up"
vm.eventValue = value;
});
$scope.$on('$destroy', unSubscribe);
});
Parent Controller Usage
app.controller("parent", function($scope, eventRecorder) {
var vm = $scope;
vm.onEvent(value) {
eventRecorder.notify(value);
});
});
In order to prevent memory leaks, controllers should unsubscibe on destruction of the scope.
This example shows an event recorder service that provides only one feed. It can be generalized to handle more that one feed.
UPDATE: Automatic Unsubscribe
"In order to prevent memory leaks, controllers should unsubscibe on destruction of the scope." I know what you mean, we had similar notification service, but we pulled it out, because somebody would always forget to unsubscribe.
One could require scope as a subscribe function argument and implement automatic unsubsciption.
this.subscribe = function evSubscribe(scope, callbackFn) {
//callback for subscriber to "catch-up"
if (lastEvent) { callbackFn(lastEvent) };
//save subscriber
subscriberIdNum++
var subscriberIdString = 'id'+subscriberIdNum;
subscriberList[subscriberIdString] = callbackFn;
var unSubscribe = function evUnsubscribe() {
delete subscriberList[subscriberIdString];
};
scope.$on("$destroy", unSubscribe);
return unSubscribe;
};

Socket.io run function on update

How do I run a function when socket.io updates a variable. It seems that $watch does not work or purhaps I am doing something wrong.
$http.get('/api/availabilitys/' + myId).then(response => {
$scope.availability = response.data;
socket.syncUpdates('availability', $scope.availability);
});
$scope.$watch('availability', function() {
console.log('test'); //This is not printing on update
angular.forEach(self.location, function (loc){
loc.availability = $scope.availability.filter(function (a){
return a.loc === loc._id;
});
});
});
The functions have to be in the same controller / scope because of encapsulation. The $scope of angular does not know if socket.io updates a variable, use a socket.on() listener to trigger the angular $watch
Try the code in this thread Socket.IO message doesn't update Angular variable
Instead of $watch in angular you can use socket.on() of socket.io
var container = angular.module("AdminApp", []);
container.controller("StatsController", function($scope) {
var socket = io.connect();
socket.on('message', function (msg) {
console.log(msg);
$scope.$apply(function() { $scope.frontEnd = msg; });
});
});
See (the posts at the end of) this thread
How do I use $scope.$watch and $scope.$apply in AngularJS?

$interval cancel angularjs not working

I'm tring to call 2 functions, and each one cancel the other. I'm using ionic and angularjs $interval. This is my code:
var promise;
var promise2;
$scope.$on('mapInitialized', function(event, map) {
var justLocation = window.localStorage.getItem('justLocation');
var tracage = window.localStorage.getItem('tracage');
//Location defined function
//traceDirection defined function
if(justLocation==1){
$interval.cancel(promise2);
promise = $interval(Location, 5000);
} else if(tracage==1) {
$interval.cancel(promise);
promise2 = $interval(traceDirection, 5000);
}
});
But when I'm running the code, the 2 function running at the same time, please any idea about my problem

Controlling order of execution in angularjs

I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}

Resources