Angular multiple instances of the same controller are created - angularjs

In my example below you can see that each time you go to a page a new instance of the same controller is created with a different id, and the old one is not destroyed, the setIntervat method keeps logging the corresponding scope id and fruit name on all of them. Let's say I have an ajax call instead of the log, that refreshes the page's content from time to time, but I don't want to make calls for the inactive pages, how can I fix this?
example
var app = angular.module('plunker', ['ngRoute']);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/fruit/:fruitId', {
templateUrl: 'fruit-tpl.html',
controller: 'FruitController'
})
.otherwise({
redirectTo: '/fruit/1'
});
}]);
app.controller('FruitController', ['$scope', '$routeParams',
function($scope, $routeParams) {
var fruitArr = ['Lemons', 'Oranges', 'Grapefruit'],
fruitId = $routeParams.fruitId - 1;
$scope.fruitName = fruitArr[fruitId];
setInterval(function() {
console.log('scope', $scope.$id, $scope.fruitName);
}, 3000);
}
]);

setInterval does not get stopped automatically when a controller is destroyed, and until it is stopped, the variables closed on by the closure you pass to it will remain in memory. Even if you switched to $interval, the same problem would persist.
Note: Intervals created by this service must be explicitly destroyed when you are finished with them. In particular they are not automatically destroyed when a controller's scope or a directive's element are destroyed. You should take this into consideration and make sure to always cancel the interval at the appropriate moment. See the example below for more details on how and when to do this.
var theInterval = setInterval(function() {
console.log('scope', $scope.$id, $scope.fruitName);
}, 3000);
$scope.$on('$destroy', function() {
// Make sure that the interval is destroyed too
clearInterval(theInterval);
});

Related

Angular 1 - share data from rest call to another controller

I have two partial pages. In the first page a rest call is made in the ManageUsersTreeViewController. I want this data to get to the second controller ManageUsersTableController. $scope.userData = data; in the getUserData() function is what i need to get to the second controller.
First Controller
app.controller('ManageUsersTreeViewController',['$rootScope', '$scope', '$http', function($rootScope, $scope, $http) {
$scope.getAllUsers = function() {
$http.get('rest/useraccess/users/').
success(function (data) {
$scope.users = data;
}).
error(function (error) {
console.log(error);
});
};
$scope.getUserData = function(user){
$http.get('rest/useraccess/' + user ).
success(function (data) {
$scope.userData = data;
console.log("user data returned:" + $scope.userData);
}).
error(function (error) {
console.log(error);
});
};
}]);
Second Controller
app.controller('ManageUsersTableController',[ '$scope', '$http', '$rootScope', function($rootScope, $scope, $http) {
$scope.maxSize = 3;
$scope.pageNumber = 1;
$scope.pageSize = 20;
$scope.pageSizesForSelect =[5,10,20,50];
}]);
How can i share the $scope.userData in the ManageUsersTreeViewController to the ManageUsersTableController? Any help is much appreciated.
you can use $emit function. this stackoverflow answer explains $emit, $broadcast, $on.
app.controller('ManageUsersTreeViewController',['$rootScope', '$scope', '$http', function($rootScope, $scope, $http) {
$rootScope.$emit('ManageUserData.Event_or_name_it_yourself', { data: $scope.userData });
}
then, in your other controllers, you can listen to this event. *make sure your view loaded both controllers.
app.controller('ManageUsersTableController',[ '$scope', '$http', '$rootScope', function($rootScope, $scope, $http) {
// destory the rootscope watcher on scope destory event, as garbage collection. otherwise your rootscope will keep listen on the event
$scope.$on('$destory', $rootScope.$on('ManageUserData.Event_or_name_it_yourself', function (event, params) {
$scope.userData = params.data;
// do what you need to do.
});
}
You're probably going to want to build an angular service to share that data across controllers. Another "down n dirty" option is to attach your userData to the $rootScope (shown below). I think you'll find your app will outgrow that solution quickly, however.
$rootScope.getUserData = function(user){
$http.get('rest/useraccess/' + user ).
success(function (data) {
$rootScope.userData = data;
console.log("user data returned:" + $rootScope.userData);
// ... now just access $rootScope.userData in other controllers
}).
error(function (error) {
console.log(error);
});
You cannot use $emit, $broadcast, $on inorder to message data between controllers. the problem with this approch is that the receiving controller may not be loaded yet and there for didnt register it's $on function.
For example if you have 2 page app with seperate controller when the first is loaded it's controller is loaded as well but the seconde page controller isn't there for there will be no controller to catch the first controller $emit or $broadcast.
The above will work only if all involved controllers were allready loaded.
As metion before you can use a service to do the work for you and store the receiving data in the $rootScope. The probllem with this approach is that when your app will grow bigger and will have more and more controller that you will want to communicate between each other , storing all this data in the rootScope is bad approach.
If you wish to use a service, i suggest that you will design a communication system as follow.
Create an array at the size of the number of your conroller in your service.
Each cell in the above array will also be an array , starting with the size of zero.
Inside this service create a const array containing all of your controllers names.
inject ths service into each one of your controllers.
Each Time a controller wants to send data to another controller, it need to iterate the service's names array until it finds the controller name it want to pass data to. one the receiving controller name is found the sending controller know it's index and can push a new message into the receiving controller cell in the array described in 1+2.
The "message" can be an object such as : {from:'controller-name', data:{}}.
when a controller start runing it check it's "mailbox" retrive the data and delete the cell if it no longer needed.
Another way you can try out is the creat a Root controller and place it on you body element in your index, such as : ...
this controller will run at the moment your app start and can use to store data as i described above

AngularJS watch value within service

I'm currently developing an AngularJS web application.
I have a primary view (Index), child view (Dashboard) and grandchild view (Raiding The Rails).
http://localhost:4000/#/dashboard/raiding-the-rails/1
Within the grandchild view (Raiding The Rails) I am displaying dress information relevant to the state ID /1, each dress has a specified state ID e.g /1,/2,/3.
I have a controller/service sending the state ID to a console.log (within the parent) and when viewing raiding-the-rails/1 the console.log displays {stateID: "1"}, If I change the URL to raiding-the-rails/4 the console.log doesn't update unless I refresh the page.
Also, When I completely refresh the browser the console.log spits out three objects instead of one?
I've reviewed many sites and have tried and tried again trying to figure this out, I even tried setting up a Watch service but this failed massively.
If anyone could help me out I would be highly grateful!Thank you.
App:
(function(angular, undefined){
"use strict";
var am = angular.module('virtual-fitting', ['ui.router']);
am.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: '../views/dashboard.html'
})
.state('dashboard.raidingtherails', {
url: '/raiding-the-rails',
templateUrl: '../views/dashboard.raiding-the-rails.html'
})
.state('dashboard.raidingtherails.dress', {
url: '/:id',
templateUrl: '../views/dashboard.raiding-the-rails.dress.html'
});
$urlRouterProvider.otherwise('/');
});
})(angular);
Service:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.factory('raidingService', function () {
var raidingService = {
stateID: null
};
return raidingService;
});
})(angular);
Parent Controller:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.controller('dashboardCtrl', function(raidingService) {
console.log(raidingService);
});
})(angular);
Child Controller:
(function(angular, undefined) {
"use strict";
var am = angular.module('virtual-fitting');
am.controller('raidingtherailsCtrl', function($state, $stateParams, raidingService) {
var self = this;
raidingService.stateID = $stateParams.id;
});
})(angular);
I assume it's in the parent controller that you want to watch your service?
If so, you could watch a function, like so:
$scope.$watch(function() {
return raidingService.stateId;
}, function(value) {
console.log(value);
}, true);
That should work.
MVC pattern used in any framework:
You creating Service with .get() and .set() methods. Set method is common to be used in any place you desire to operate model value and allows you to create one point that will handle changes to your model. The final move in .set() method is .$boradcast() notifying whole application about changes to your model.
Pros:
one access point to value
application is know about any changes
no need to write $watch with watching collections (holly-molly)
having access point to changes with '$on' in your controllers $scope
Cons:
easy to forget to use .set() method instead of simple assignment

$on only getting called on one controller

I have a controller that has an event called changeSafe. I want other controllers to listen when this event gets fired
secure.controller('wrapperCtrl', function ($scope, $rootScope, storeFactory, safeFactory) {
$scope.changeSafe = function (safeId) {
$scope.loading = true;
safeFactory.setSafe(safeId).then(function (safeData) {
$scope.safe = safeData;
$scope.loading = false;
$rootScope.$broadcast('changeSafe', safeData);
});
}
});
The first page that loads is called dashboard when I add what is below the page re-draws with $scope.safe as I would expect it to.
secure.controller('dashboardCtrl', function ($scope, $rootScope, storeFactory, safeFactory) {
$scope.$on('changeSafe', function (event, arg) {
bindSafe(arg.safeId);
});
});
I have pretty much the exact same thing on my history Controller
secure.controller('historyCtrl', function($scope, $rootScope, storeFactory, safeFactory) {
$scope.$on('changeSafe', function (event, arg) {
bindHistory(arg.safeId);
});
});
Here is I have in the config section
secure.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/history', {
templateUrl: '/Angular/History/history.html',
controller: 'historyCtrl'
})
.otherwise({
templateUrl: '/Angular/Dashboard/dashboard.html',
controller: 'dashboardCtrl'
});
}]);
Whenever I click the button that is held within wrapperCtrl only the $scope.$on fires in the dashboardCtrl. Can anyone see why the $scope.$on is not being fired from the historyCtrl controller? I'm also unclear why it gets called from dashboardCtrl when I'm not on that view anymore.
When I step through the code, I'm actually seeing the $rootScope.$on('changeSafe') getting called multiple times both on history page and dashboard page. I can't figure out why it is changing views back to the dashboard though
I'm not sure if I have a full grasp of your problem, but my best guess is you are having a load order issue and the event is being broadcasted before the subscription has been initialized.
First off, to save yourself a lot of headaches, it is important to have a solid understanding of Event Handling in AnguarJS:
know the difference between subscribing on $rootScope.$on vs
$scope.$on
know the difference of publishing with $broadcast vs $emit on
$rootScope vs $scope
From the comments it sounds like you might have been using $rootScope.$on, which isn't cleaned up when your controllers are destroyed (same as directives):
var changeSafeListnerUnbindFunction = $rootScope.$on('changeSafe', funciton() { //... });
$scope.$on('$destroy', changeSafeListnerUnbindFunction);
Given your use case, registering listeners on the child scopes will pick up events published from the $rootScope.$broadcast (which communicates top level down to each child scope).
You probably have a load order issue?

How to transfer variables from a scope to another in Angular?

// Somewhere in FooCtrl
$scope.myvar = 'titi'
$location.path('/viewSubscribe'); // redirection to the subscribe page
I'd like to be able to retrieve the "myvar" variable in the $scope of SubscribeCtrl (linked to /viewSubscribe route).
What is the clean way to do it?
Thanks.
Well, there are several ways, depending on the use case. Here are some approaches ordered by level of relevancy to yours:
Route it (demo)
To the path
Pass it as a path variable, and define the variable name in when routing (in the $routeProvider configuration):
// somewhere
$scope.myvar = 'titi';
$location.path('/viewSubscribe/' + $scope.myvar);
// ...
// in the module configuration
app.config(function ($routeProvider) {
$routeProvider
.when('/viewSubscribe/:myVar', {
templateUrl: 'views/myView.html',
controller: 'MyCtrl'
})
// etc.
});
Then you can inject the route parameters service to the desired location, and get the path variable's value:
// somewhere else
app.controller('MyCtrl', function($routeParams, $log) {
$log.info($routeParams['myVar']); // --> 'titi'
});
Shoot it (demo)
As in "event fired"
You can fire events to scopes up or down the hierarchy with the scope's $emit() or $broadcast(), respectively, e.g.:
app.controller('ParentCtrl', function($scope) {
$scope.$watch('model.text', function(newVal) {
$scope.$broadcast('myVarChanged', newVal);
});
});
app.controller('ChildCtrl', function($scope, $log) {
$scope.$on('myVarChanged', function (myVar) {
$log.info(myVar); // --> 'titi'
});
});
Fix it (demo)
As in "for good"
You can also set it once, and get it everywhere, if you declare it as a constant (or a value):
// at some outer level
app.constant('myVar', 'titi');
// anywhere, really
app.controller('MyCtrl', function(myVar, $log) {
$log.info(myVar); // --> 'titi'
});
Plant it (demo)
So it has roots
Declare it in the root scope, so it is shared to all scopes:
app.run(function ($rootScope) {
$rootScope.myVar = 'titi';
});
// later
app.controller('MyCtrl', function($rootScope, $log) {
$log.info($rootScope.myVar); // --> 'titi'
$log.info($scope.myVar); // --> 'titi'
});
This method is less recommended, as it sets a common property for all inherited scopes, which can be easily overridden or shadowed (scopes are inherited prototypaly) and it cannot be used with isolated scopes (i.e. in directives that define their scope as such).
In the case you don't want to retrieve the variable as a path parameter you can also make use of services. These are guaranteed to last for the duration of an AngularJS app.
See http://docs.angularjs.org/guide/services
You can then store the variables inside your service as you would from a normal Java-like class and pass the services via dependency injection.

Angular getting access to $routeParams in a controller not connected to the route

I have a SPA Angular app which has two main html divs. One of these changes frequently and the other less frequently. The frequently changing one uses $route in the app.config. The less frequently changing div also needs to be aware of url changes, so I want to listen for changes in $routeParamas and do stuff accordingly.
So in my controller I have:
.controller('SecondaryCtrl', function ($scope, $routeParams, MyService) {
$scope.$watch('routeParams', function () {
console.log('routeParams: ', $routeParams);
// I need $routeParams.projectId to compose an API call.
});
...
});
In the console, $routeParams is returned as an empty object. I know that if the Controller and route are defined in the app.config, the$routeParams are available, but the app.config already has definition for the given route which links to the PrimaryCtrl:
.config( function ($interpolateProvider, $routeProvider) {
$routeProvider
.when('/project/:projectId', {
templateUrl : 'partials/_project_detail.html',
controller: 'PrimaryCtrl',
resolve: {
project: function ($route, MyService) {
return MyService.get('projects/', $route.current.params.projectId);
},
}
})
So, how can I get access to the $routeParams in my SecondaryCtrl when $routeParams changes?
You can listen to the $routeChangeSuccess event inside of watching $routeParams, like this:
.controller('SecondaryCtrl', function ($scope, MyService) {
$scope.$on('$routeChangeSuccess', function (event, current, previous) {
// current is the current route
// previous is the previous route
});
...
});
Angular Doc

Resources