Maintain Angular $scope on navigation - angularjs

I have an Angular app with several controllers. I would like each controller to maintain its state even when the user navigates to another controller and back again. Essentially I want each view to load up just as the user last saw it (unless they navigate away from the app entirely or refresh etc.).
I understand how to share state between controllers using a service but this is not what I want. I could create a new service for every controller or put everything on the $rootScope but it seems wrong.
What is the recommended way to do this?

I would store any persistent data in the $cookieStore and maybe even encapsulate it in a service to avoid remembering what the interface to the cookie actually is. Here's an example:
var app = angular.module('app', [
'ngRoute',
'ngCookies'
]);
app.service('ticketService', function ($cookieStore) {
this.getTicket = function () {
var ticket = $cookieStore.get('currentTicket');
// Check server for ticket? (additional business logic)
return ticket;
};
this.setTicket = function (ticket) {
// Validate correct ticket? (additional business logic)
$cookieStore.set('currentTicket', ticket);
};
});
app.controller('ticketController', function ($scope, ticketService) {
var defaultTicket = {
name: '',
description: ''
};
$scope.ticket = ticketService.getTicket() || defaultTicket;
$scope.$watch('ticket', function (oldValue, newValue) {
if (oldValue === newValue) { return; }
ticketService.setTicket(newValue);
});
});

Related

Data in my service isn't persistent across my angular controllers

I have two controllers. The first one sets a variable into my service and my second one is supposed to get this variable, but it's undefined.
Aren't angular services supposed to be singletons ? Because my service seems to be instantiated for each controller.
Here's my code :
First controller
angular.module('myApp').controller('HomeCtrl', ['$scope', 'User', function ($scope, User) {
$scope.join = function () {
User.setRoom("test");
console.log(User.getRoom()); // displays 'test'
$window.location.href = '/talk';
}
}]);
In my second controller, I've just a
console.log(User.getRoom()); // displays ''
And here's my service
angular.module('myApp').factory('User', function () {
var data = {
room: ''
};
return {
getRoom: function () {
return data.room;
},
setRoom: function (room) {
data.room = room;
}
};
});
Do you have an idea?
You are using $window.location.href = '/talk'; to navigate - this triggers a full page reload, and all services are therefore also reinitialized.
You probably want to use the $location service. See the documentation and/or this answer for a summary of the difference between the two.

Refreshing data from service in controller

I have two separate controllers: AuthController and NavController.
AuthController is responsible for running registration/login form, and NavController is responsible for displaying navbar where I want to show current username if one is logged in. Finally, I have service "auth" that handles all that register/login stuff
auth service have this function:
auth.currentUser = function() {
if (auth.isLoggedIn()) {
var token = auth.getToken();
var payload = this.decodeUsername(token);
return payload.username;
}
};
and NavController looks like this:
app.controller('NavController', ['$scope', 'auth',
function($scope, auth) {
$scope.isLoggedIn = auth.isLoggedIn;
$scope.logOut = auth.logOut;
$scope.currentUser = auth.currentUser();
}
]);
So i can display current username, but if user just logged in NavController "doesn't know" that anything changed. I've tried to use event, but this two controllers doesn't have parent-child relation. Should I wrap them in one parent controller and do "AuthController-emit->SuperController-broadcast->NavController" or there is better way to communicate there two controllers?
You have two options:
Use $rootScope.broadcast (example here) and this will send an event from the top down to every controller. This works best if multiple things will want to see this message.
Or if you only ever want the navbar to be notified you could use a callback.
In your auth service have a function that gets called on state change such as
authApi.stateChange = function() {}
In your nav bar controller you then set authApi.stateChange = $scope.authUpdated; and then your authUpdated function will be notified from the service when authApi.stateChange() is called
When there is something to be shared between controllers, a Service would the best way to achieve the result. As its singleton, there will be only one instance, and your controllers - 'Auth' - can set/update value, 'Nav' can bind to the changes.
If there is some fetching involved use promise. And if the data is going to be fetched only once then you are better off by just using promise.
auth.currentUser = function() {
var defer = $q.defer();
if (auth.isLoggedIn()) {
var token = //some asynoperation//;
var payload = this.decodeUsername(token);
defer.resolve(payload.username);
}else{
defer.reject("Not logged in");
}
return defer.promise;
};
(//do remember to inject $q)

How to pass dynamic data from one module controller to another module controller

I'm new to AngularJS, I want to pass dynamic value (username) from one controller in one module to another controller in another module. Routing and other things are working fine.
This is my code
loginModule.js
(function() {
var app = angular.module("waleteros", ["ngRoute","ui.bootstrap","ngCookies"]);
app.config(function($routeProvider) {
$routeProvider
.when("/",{
templateUrl:"views/login.html",
controller:"LoginCtrl"
})
}
})
app.js
(function() {
var app = angular.module("waleterosAdmin", ["ngRoute","ngGrid","ui.bootstrap","ngCookies"]);
app.config(function($routeProvider) {
$routeProvider
.when("/home",{
templateUrl:"homepage.html",
controller:"HomeCtrl"
})
}
})
loginCtrl.js
(function(){
var app = angular.module("waleteros");
var LoginCtrl= function($scope,$location)
{
$scope.signIn=function(email,pin)
{
//Some authentication code...
//Here i want to pass the username to homectrl.js
window.location.href="views/home.html"
}
}
app.controller("LoginCtrl", LoginCtrl);
})
HomeCtrl.js
(function(){
var app = angular.module("waleterosAdmin");
var HomeCtrl=function($scope)
{
//Here i want to get the username
}
app.controller("HomeCtrl", HomeCtrl);
})
you can share service between modules ,and thus pass value between modules,
please have a look here Share a single service between multiple angular.js apps ,and here sharing between modules with AngularJS?
You would use a service to persist the data, and then inject the service into your controllers:
app.service("SharedProperties", function () {
var _userName = null;
return {
getUser: function () {
return _userName
},
setUser: function(user) {
_userName = user;
}
}
});
Now inject SharedProperties and use the getter/setter
var LoginCtrl= function($scope,$location, SharedProperties)
{
$scope.signIn=function(email,pin)
{
//Some authentication code...
SharedProperties.setUser(user);
//Here i want to pass the username to homectrl.js
window.location.href="views/home.html"
}
}
var app = angular.module("waleterosAdmin");
var HomeCtrl=function($scope, SharedProperties)
{
//Here i want to get the username
var user = SharedProperties.getUser();
}
One word of warning about services is that they persist for the lifetime of the application, i.e. they are only instantiated once. I have run into scenarios, especially once routing is implemented, where I want to wipe the data off of the service to save space and replace it with new data (you don't want to keep adding to this service every time you look at a different view). To do this, you could either write a "wipe" method that you call to clean the service on the route changes, or stick the data into a directive (on its controller), put the controllers into their own directives, and have these require the first directive, so that the data is accesible from the controller's with the added benefit of being wiped once the DOM element is declared on is destroy (on a view change, for instance).

Preserve state with Angular UI-Router

I have an app with a ng-view that sends emails to contact selected from a contact list.
When the users select "Recipient" it shows another view/page where he can search, filter, etc. "Send email" and "Contact list" are different html partials that are loaded in the ng-view.
I need to keep the send form state so when the users select someone from the Contact List it returns to the same point (and same state). I read about different solutions ($rootScope, hidden divs using ng-show, ...) but I want to know if UI-router will help me with it's State Manager. If not, are there other ready-to-use solutions?
Thanks!
The solution i have gone with is using services as my data/model storage. they persist across controller changes.
example
the user service ( our model that persists across controller changes )
app.factory('userModel', [function () {
return {
model: {
name: '',
email: ''
}
};
}]);
using it in a controller
function userCtrl($scope, userModel) {
$scope.user = userModel;
}
the other advantage of this is that you can reuse your model in other controllers just as easly.
I'm not sure if this is recommended or not, but I created a StateService to save/load properties from my controllers' scopes. It looks like this:
(function(){
'use strict';
angular.module('app').service('StateService', function(){
var _states = {};
var _save = function(name, scope, fields){
if(!_states[name])
_states[name] = {};
for(var i=0; i<fields.length; i++){
_states[name][fields[i]] = scope[fields[i]];
}
}
var _load = function(name, scope, fields){
if(!_states[name])
return scope;
for(var i=0; i<fields.length; i++){
if(typeof _states[name][fields[i]] !== 'undefined')
scope[fields[i]] = _states[name][fields[i]];
}
return scope;
}
// ===== Return exposed functions ===== //
return({
save: _save,
load: _load
});
});
})();
To use it, I put some code at the end of my controller like this:
angular.module('app').controller('ExampleCtrl', ['$scope', 'StateService', function ($scope, StateService) {
$scope.keyword = '';
$scope.people = [];
...
var saveStateFields = ['keyword','people'];
$scope = StateService.load('ExampleCtrl', $scope, saveStateFields);
$scope.$on('$destroy', function() {
StateService.save('ExampleCtrl', $scope, saveStateFields);
});
}]);
I have found Angular-Multi-View to be a godsend for this scenario. It lets you preserve state in one view while other views are handling the route. It also lets multiple views handle the same route.
You can do this with UI-Router but you'll need to nest the views which IMHO can get ugly.

AngularJS - Shared state between controllers?

I know there are other similar questions on how to pass data between Angular controllers.
What I wonder is how to deal with this in a view..
Lets say I have a UserController for login, registration etc.
And an AppController for the actual app functionallity .
The UserController would be fairly easy, its sort of standalone from the rest.
But what if the app needs to know about stuff from the user controller?
Lets say the app view needs to hide/show stuff depending on if the user is logged in or not.
Or it could be if the user is male or female etc.
Should the app model keep its own copy of the user model state?
e.g. appModel.isLoggedIn , appModel.gender etc ?
feels a bit redundant, but at the same time more testable.
So what is the correct way to do this?
Short answer
Create a service, see Creating Services for details.
Long answer
Services are - per se - application-wide singletons, hence they are perfect for keeping state across views, controllers & co.:
app.factory('myService', [ function () {
'use strict';
return {
// Your service implementation goes here ...
};
}]);
Once you have written and registered your service, you can require it in your controllers using AngularJS' dependency injection feature:
app.controller('myController', [ 'myService', '$scope',
function (myService, $scope) {
'use strict';
// Your controller implementation goes here ...
}]);
Now, inside your controller you have the myService variable which contains the single instance of the service. There you can have a property isLoggedIn that represents whether your user is logged in or not.
To further specify the answer #GoloRoden gave, this is an example of how you can share state values across all controllers taking the service as a dependency.
App.factory('formState', formState);
function formState() {
var state = {};
var builder = "nope";
var search = "nope";
state.builder = function () {
return builder;
};
state.search = function () {
return search;
};
state.set = {
'builder': function (val) {
builder = val;
},
'search': function (val) {
search = val;
}
};
return {
getStateManager: function () {
return state;
}
};
}
App.controller('builderCtrl', builderCtrl);
builderCtrl.$inject = ['formState']
function builderCtrl(formState) {
var stateManager = formState.getStateManager();
activate();
function activate() {
console.log("setting val in builder");
stateManager.set.search("yeah, builder!");
console.log("reading search in builder: " + stateManager.search());
console.log("reading builder in builder: " + stateManager.builder());
}
}
App.controller('searchCtrl', searchCtrl);
searchCtrl.$inject = ['formState']
function searchCtrl(formState) {
var stateManager = formState.getStateManager();
activate();
function activate() {
console.log("setting val in search");
stateManager.set.search("yeah, search!");
console.log("reading search in search: " + stateManager.search());
console.log("reading builder in search: " + stateManager.builder());
}
}

Resources