In my controller I am storing data as $scope.$parent.dossierSummaries = data;
but after log out and login the application $scope.$parent.dossierSummaries retains the same old data.
I am doing this on log out
.success( function( response, status ) {
if ( response.status > 0 ) {
var u = $rootScope.user.username;
$cookieStore.remove('myapp');
$rootScope.user = { username: '', role: 0 };
success(u);
}
else {
error(response.messages);
}
})
.error( function( response, status ) {
error(['There was an error logging you out.']);
});
in angularJS, you shouldn't set the variable directly to a controller but you should retrieve it from a service instead. So whenever you load a controller you should write a init() function to get value of that model. So everytime you will have the correct data from server.
Code example and docs : http://docs.angularjs.org/guide/dev_guide.services.creating_services
Another approach to manually tracking and cleaning things up would be to broadcast a 'logout' event on the rootScope (or other custom event). Then listen for the event either in your controller or in your service to clean up the data.
Broadcast:
$rootScope.broadcast('logout');
Watching for an event (in a service for example):
$rootScope.on('logout',function(){
dossiers = [];
});
I don't think there is any effective way to achieve it. Any object (controller, directive,filter or as a matter of fact any js object) can hold reference to another object (in your case user), and one cannot determine easily who all are holding reference.
The reference would only get release if you do it either explicitly or when the object holder the reference is destroyed.
What you can try is
$rootScope.user.username='';
$rootScope.role=0;
Assuming some object are tracking this specific object the data would be cleared now.
if you don't mind a slight screen flickering on logout, you can refresh the page using a method like this:
$window.location.replace($window.location.toString().split('#')[0]);
this would clear out all the $scope and $rootScope variables, and in my case has solved the issue.
If you want to clear the $scope, I think you can use it's constructor method or proto (prototype), which is used in constructor. I believe you can do that to reset the scope to initial state. If someone knows any more on this, feel free to comment.
Related
I am using the q service in one of my controllers to make sure my requests finish before binding the responses in the then clause. Now here is the tricky part. There is a directive on the page who's template updates a scope variable. This scope variable is used to switch between different parts of the response json, A selector if you will. I need to updated a variable set in the then clause after the page is loaded. It is set by the id added in a directive.
I can't seem to figure out an efficient way to go about updating them.
$scope.selector = {}; //property added from a child scope
$q.all({
//some factory calls and assignment to properties
}).then(function(responses){
//scope variable assignments off of the responses object.
//some assignment that uses the selector. a[selector.id] ex.
}, function(err){
$log.error(err);
}).finally(function(){
//some setting of load params
});
//Then I need to update those variables set in the then based on whether or not the selector id was changed in the directive template.
Any help would be greatly appreciated.
Taking a guess here as the question isn't clear but from the looks of it, you should just save the entire set of responses on the scope and then pull out the data you need. I don't see why you are trying to update the entire response everytime you want to pull one aspect out.
$scope.selector = {}; //property added from a child scope
$scope.responses = {};
$q.all({
//some factory calls and assignment to properties
}).then(function(responses){
//scope variable assignments off of the responses object.
//some assignment that uses the selector. a[selector.id] ex.
$scope.responses = responses;
}, function(err){
$log.error(err);
}).finally(function(){
//some setting of load params
});
// Use something like $watch here and have it call a function
Watchers in AngularJS might be helpful as well.
My angular app have 2 controllers. My problem is that the controllers does not keep the data when the user navigates away from the page.
How can I store the selected data on of my controllers into a data store so it can be used between other controllers?
Option 1 - custom service
You can utilize a dedicated angular service to store and share data between controllers (services are single instance objects)
service definition
app.service('commonService', function ($http) {
var info;
return {
getInfo: getInfo,
setInfo: setInfo
};
// .................
function getInfo() {
return info;
}
function setInfo(value) {
info = value;
}
});
usage in multiple controllers
app.controller("HomeController", function ($scope, commonService) {
$scope.setInfo = function(value){
commonService.setInfo(value);
};
});
app.controller("MyController", function ($scope, commonService) {
$scope.info = commonService.getInfo();
});
Option 2 - html5 localStorage
You can use the built-in browser local storage and store your data from anywhere
writing
$window.localStorage['my-data'] = 'hello world';
reading
var data = $window.localStorage['my-data']
// ...
check out this awesome project:
https://github.com/grevory/angular-local-storage
Option 3 - via web server api
If you need to persist data among different users, you should save it somewhere in the server side (db / cache)
function getProfile() {
return $http.get(commonService.baseApi + '/api/profile/');
}
function updateProfile(data) {
var json = angular.toJson(data);
return $http.post(commonService.baseApi + '/api/profile/', json);
}
EDIT See Jossef Harush's answer where he has written an in-depth response that covers other methods including this one.
I'd recommend using either localStorage or sessionStorage - http://www.w3schools.com/html/html5_webstorage.asp.
HTML local storage provides two objects for storing data on the client:
window.localStorage - stores data with no expiration date
window.sessionStorage - stores data for one session (data is lost when the browser tab is closed)
This assumes that you don't want to POST/PUT the data to your web service (windows service mention in your question).
If you data is an array or some sort, you can convert it to JSON to store as a string and then when you need it you can parse it back as follows - How do I store an array in localStorage?:
var names = [];
names[0] = prompt("New member name?");
localStorage["names"] = JSON.stringify(names);
//...
var storedNames = JSON.parse(localStorage["names"]);
There is an option not mentioned in other answers (AFAIK).
EVENTS
You can use events for communication between controllers.
It's a straightforward communication that doesn't need a mediator
(like service) and can't be wiped by the user (like HTML storage).
All the code is written in controllers that you are trying to
communicate with and thus very transparent.
A good example how to leverage events to communicate between controllers can be seen below.
The publisher is the scope that wanna publish (in other words let others know something happened). Most don't care about what has happened and are not part of this story.
The subscriber is the one that cares that certain event has been published (in other words when it gets notified hey, this happened, it reacts).
We will use $rootScope as a mediator between publisher and a subscriber. This always works because whatever scope emits an event, $rootScope is a parent of that scope or parent of a parent of a parent.. When $rootScope broadcasts (tells everyone who inherits) about an event, everyone hears (since $rootScope is just that, the root of the scope inheritance tree) so every other scope in app is a child of it or child of a child of a child..
// publisher
angular.module('test', []).controller('CtrlPublish', ['$rootScope','$scope',
function ($rootScope, $scope) {
$scope.send = function() {
$rootScope.$broadcast('eventName', 'message');
};
}]);
// subscriber
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {
$scope.$on('eventName', function (event, arg) {
$scope.receiver = 'got your ' + arg;
});
}]);
Above we see two controllers communicating a message to each other using an event. The event has a name, it has to be unique, otherwise, a subscriber doesn't differentiate between events. The event parameter holds autogenerated but sometimes useful data, the message is the payload. In this example, it's a string but it can be any object. So simply put all the data you wish to communicate inside an object and send it via event.
NOTE:
You can avoid using root scope for this purpose (and limit the number of controllers that get notified of an event) in case two scopes are in direct inheritance line of each other. Further explanation below:
$rootScope.$emit only lets other $rootScope listeners catch it. This is good when you don't want every $scope to get it. Mostly a high level communication. Think of it as adults talking to each other in a room so the kids can't hear them.
$rootScope.$broadcast is a method that lets pretty much everything hear it. This would be the equivalent of parents yelling that dinner is ready so everyone in the house hears it.
$scope.$emit is when you want that $scope and all its parents and $rootScope to hear the event. This is a child whining to their parents at home (but not at a grocery store where other kids can hear). This is a shortcut to use when you wanna communicate from the publisher that is a child or n-th child of the subscriber.
$scope.$broadcast is for the $scope itself and its children. This is a child whispering to its stuffed animals so their parents can't hear.
EDIT: I thought plunker with a more elaborate example would be enough so I decided to keep is simple here. This elaborate explanation should be better.
To share data between two controllers on the same page, you can use factories/services. Take a look at Share data between AngularJS controllers for example.
However, if this is across page reloads/refreshes, you will need to store the data in local storage and then read it upon reloading. An example of that is here: How do I store data in local storage using Angularjs?
Checkout this library https://www.npmjs.com/package/angularjs-store
This can help you manage your application state much simpler as it will force you to have a one way data flow on your application.
I have a user-service taking care of the authentication.
In the html I have:
<div ng-show="user.authenticated">Hi {{user.username}}!</div>
The controller sets up the scope like this:
$scope.user = userService;
This works great! If I am reloading the html-page, the div is hidden for a short while until the already logged in user is authenticated.
But if I try to set up the user-object on $scope in the controller like this:
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
If I reload the page, then it does not work any more, the div is never shown, even if the user already is logged in, like in the above example. How this is not working?
Edit: I will add the controller (or at least, the part of the controller that is interesting here):
angular.module('app.controllers')
.controller('NavCtrl', ['$scope','$rootScope','$location','userService','alertService',
function($scope,$rootScope,$location,userService,alertService) {
// $scope.user = userService; // This works (but is now commented out)
// The following does not work if the user reloads this page
// The view is only updated with username when (after a few milliseconds)
// userService has talked with the server and gotten the user-details...
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
}]);
It is important to reload the html after login, or else the userService will already be set up with the user-details. So when the view is set up (after page-reload), there is no user-info available in the userService, this is available only a short while after reload....
Edit 2: The reason I was trying the second variant is because I have a page with an object with various properties and the username is only one of those properties needed. This works fine until the user possibly reloads the page.
Edit 3: I have altered 2ooom's fiddle to make it more my case, see http://jsfiddle.net/utphjuzy/1/
This as to do with how Angular's binding mechanism works (and how binding works in general).
When you do something like $scope.user = userService;, you are actually binding the property user to the Object userService. That means that both properties will point to the same in-memory mutable object reference. If a property of that object changes, then both "pointers" will notice that change (in the case of the $scope.user, that will force a re-render).
However, if you use it like this:
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
You are creating a completely new user Object and you are assigning its properties not by reference but by value because JavaScript strings, numbers and booleans are immutable and therefore cannot be assigned by reference. Ultimately this means Angular will not be able to track changes on those properties.
One way of doing what you need is to use a model Object on the service that holds any data that needs to be bind. Like shown in this Plunker.
You are setting values which will never be updated. Your controller only runs once so this:
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
gets set to whatever values they had when the controller was created. Since I'm guessing your user service gets the details using AJAX, it will never update the user object on your scope.
Why this works?
$scope.user = userService;
here userService object is directly assigned to scope, so all updates on userService object after assigning it to scope variable will reflect into scope. for e.g
var userService = {};
$scope.user = userService;
alert($scope.user.username);
angular.extend(userService, {"username":"User"});
alert($scope.user.username);
So in above example $scope.user.username will have value User in the last statement
Why this doesn't work?
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
here values are extracted from userService and assigned to scope, so future changes to userService will not be reflected into scope. That's why $scope.user.username will be undefined
Created a jsfiddle for explaining the same
Solution
In my opinion you should initialise / re-initialize the $scope.user on success event of userService call.
I am trying to build a single page application where I have a setting form where I add a variable called Ticket Work type. When I do an update or save on the setting form, I am broadcasting the new Ticket Work Type array so that it reflects the listing as well as Ticket create page.
But the problem is that broadcast works only on the settings form page controller and not on the Ticket Add controller.
This is the code from the Settings controller where after "addTicketWorkTypes" factory method, I do a broadcast. This works :
$scope.addNewTicketWorkType = function(newticketWorkType) {
if (newticketWorkType != '') {
ticketFact.addTicketWorkTypes(newticketWorkType, $scope.workspaceId).then(function(response) {
$rootScope.$broadcast('handleTicketWorkType',response.data);
ticketFact.ticketWorkType = '';
});
}
};
And this updates the list on the same controller
/*update ticket work type when changes at any place*/
$scope.$on('handleTicketWorkType', function(events, ticketWorkType) {
$scope.ticketWorkType = ticketWorkType;
console.log('Scope updated settingsEditCtrl');
});
But the same $scope.$on doesn't work on a different controller. Can you please help.
The whole code is on Github for reference as well:
The settings controller is here: https://github.com/amitavroy/my-pm-tools/blob/master/public/assets/js/dev/settings/controllers/settingsEditCtrl.js
and the Ticket Add screen controller is here: https://github.com/amitavroy/my-pm-tools/blob/master/public/assets/js/dev/tickets/controllers/ticketAddCtrl.js
Any given scope will only be notified of events that are broadcast from a scope higher in the inheritance hierarchy. To send an event up the hierarchy use $rootScope.$emit().
If you want to be sure the event will be sent and received and you're not worried about other scopes responding to the event, you can do this:
function $broadcast() {
return $rootScope.$broadcast.apply($rootScope, arguments);
}
...
$broadcast('handleTicketWorkType', response.data);
In your other controller:
$rootScope.$on('handleTicketWorkType', function(data) {...});
If that doesn't work
It must be (as suggested by #charlietfl) that the target $scope does not yet exist and is hence not being notified of the event. In that case create a ticketWorkTypes service that is nothing but a list of ticketWorkTypes. Then replace:
$rootScope.$broadcast('handleTicketWorkType',response.data);
with
ticketWorkTypes.push(response.data);
Then inject ticketWorkTypes service (array) into any controller that needs that data.
I hate asking questions that there's already good info for but I'm missing something to keep my implementation from working.
My scenario is using a recursive function on a service to load my data in iterative chunks. All the data is captured by the scope but only the first set is displayed unless you navigate away and then back again. Clearly, I need to $watch my scope. I just can't figure out how to do so.
AccountService runs a method called getAccountsByPage which is passed an argument of 1. That function then calls itself with a value of 2, and so forth.
$routeProvider.when('/accounts/', {
...
controller: function ($scope, AccountService) {
var accounts = $scope.accounts = AccountService.getAccountsByPage(1);
$scope.$watch('accounts', function(newVal, oldVal) {
console.log(newVal, oldVal);
});
}
});
console outputs: undefined, undefined
[Object, Object, Object...] undefined
To be clear, getting the data isn't the problem. Updating the view is. Angular says not to use $watch on the controller but it seems that everyone does so...
From the code you've posted, $scope.accounts only ever gets set once when the controller first gets instantiated. It doesn't matter what AccountService.getAccountsByPage is doing underneath. Whatever it returns will be put into $scope.accounts, and trigger the $watch that one time. It won't be triggered again until $scope.accounts changes, which I don't see happening anywhere from the code posted.