AngularJS - Shared state between controllers? - angularjs

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());
}
}

Related

How to store controller functions in a service and call them in AngularJS

I need to execute functions of some controllers when my application ends (e.g. when closing the navigator tab) so I've thought in a service to manage the list of those functions and call them when needed. These functions changes depending on the controllers I have opened.
Here's some code
Controller 1
angular.module('myApp').component('myComponent', {
controller: function ($scope) {
var mc = this;
mc.saveData = function(objectToSave){
...
};
}
});
Controller 2
angular.module('myApp').component('anotherComponent', {
controller: function ($scope) {
var ac = this;
ac.printData = function(objects, priority){
...
};
}
});
How to store those functions (saveData & printData) considering they have different parameters, so when I need it, I can call them (myComponent.saveData & anotherComponent.printData).
The above code is not general controller but the angular1.5+ component with its own controller scope. So the methods saveData and printData can only be accessed in respective component HTML template.
So to utilise the above method anywhere in application, they should be part of some service\factory and that needs to be injected wherever you may required.
You can create service like :
angular.module('FTWApp').service('someService', function() {
this.saveData = function (objectToSave) {
// saveData method code
};
this.printData = function (objects, priority) {
// printData method code
};
});
and inject it wherever you need, like in your component:
controller: function(someService) {
// define method parameter data
someService.saveData(objectToSave);
someService.printData (objects, priority);
}
I managed to make this, creating a service for managing the methods that will be fired.
angular.module('FTWApp').service('myService',function(){
var ac = this;
ac.addMethodForOnClose = addMethodForOnClose;
ac.arrMethods = [];
function addMethodForOnClose(idModule, method){
ac.arrMethods[idModule] = {
id: idModule,
method: method
}
};
function executeMethodsOnClose(){
for(object in ac.arrayMethods){
ac.arrMethods[object].method();
}
});
Then in the controllers, just add the method needed to that array:
myService.addMethodForOnClose(id, vm.methodToLaunchOnClose);
Afterwards, capture the $window.onunload and run myService.executeMethodsOnClose()

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.

Issues injecting Angular factories and services

I don't know what it is about injecting factories, but I am having the most difficult time.
I've simulated what I'm attempting to do via this sample plunk http://plnkr.co/edit/I6MJRx?p=preview, which creates a kendo treelist - it works fine.
I have an onChange event in script.js which just writes to the console. That's also working.
My plunk loads the following:
1) Inits the app module, and creates the main controller myCtrl (script.js)
2) Injects widgetLinkingFactory int myCtrl
3) Injects MyService into widgetLinkingFactory
The order in which I load the files in index.html appears to be VERY important.
Again, the above plunk is NOT the real application. It demonstrates how I'm injecting factories and services.
My actual code is giving me grief. I'm having much trouble inject factories/services into other factories.
For example,
when debugging inside function linking() below, I can see neither 'CalculatorService' nor 'MyService' services. However, I can see the 'reportsContext' service.
(function () {
// ******************************
// Factory: 'widgetLinkingFactory'
// ******************************
'use strict';
app.factory('widgetLinkingFactory', ['reportsContext', 'MyService', linking]);
function linking(reportsContext, MyService) {
var service = {
linkCharts: linkCharts
};
return service;
function linkCharts(parId, widgets, parentWidgetData) {
// *** WHEN DEBUGGING HERE, ***
// I CANNOT SEE 'CalculatorService' AND 'MyService'
// HOWEVER I CAN SEE 'reportsContext'
if (parentWidgetData.parentObj === undefined) {
// user clicked on root node of grid/treelist
}
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
// REFRESH HERE...
}
});
}
}
})();
A snippet of reportsContext'service :
(function () {
'use strict';
var app = angular.module('rage');
app.service('reportsContext', ['$http', reportsContext]);
function reportsContext($http) {
this.encodeRageURL = function (sourceURL) {
var encodedURL = sourceURL.replace(/ /g, "%20");
encodedURL = encodedURL.replace(/</g, "%3C");
encodedURL = encodedURL.replace(/>/g, "%3E");
return encodedURL;
}
// SAVE CHART DATA TO LOCAL CACHE
this.saveChartCategoryAxisToLocalStorage = function (data) {
window.localStorage.setItem("chartCategoryAxis", JSON.stringify(data));
}
}
})();
One other point is that in my main directive code, I can a $broadcast event which calls the WidgetLinking factory :
Notice how I'm passing in the widgetLinkingFactory in scope.$on. Is this a problem ?
// Called from my DataModel factory :
$rootScope.$broadcast('refreshLinkedWidgets', id, widgetLinkingFactory, dataModelOptions);
// Watcher setup in my directive code :
scope.$on('refreshLinkedWidgets', function (event, parentWidgetId, widgetLinkingFactory, dataModelOptions) {
widgetLinkingFactory.linkCharts(parentWidgetId, scope.widgets, dataModelOptions);
});
I am wasting a lot of time with these injections, and it's driving me crazy.
Thanks ahead of time for your assistance.
regards,
Bob
I think you might want to read up on factories/services, but the following will work:
var app = angular.module('rage')
app.factory('hi', [function(){
var service = {};
service.sayHi = function(){return 'hi'}
return service;
}];
app.factory('bye', [function(){
var service = {};
service.sayBye = function(){return 'bye'}
return service;
}];
app.factory('combine', ['hi', 'bye', function(hi, bye){
var service = {};
service.sayHi = hi.sayHi;
service.sayBye = bye.sayBye;
return service;
}];
And in controller...
app.controller('test', ['combine', function(combine){
console.log(combine.sayHi());
console.log(combine.sayBye());
}];
So it would be most helpful if you created a plunk or something where we could fork your code and test a fix. Looking over your services it doen't seem that they are returning anything. I typically set up all of my services using the "factory" method as shown below
var app = angular.module('Bret.ApiM', ['ngRoute', 'angularFileUpload']);
app.factory('Bret.Api', ['$http', function ($http: ng.IHttpService) {
var adminService = new Bret.Api($http);
return adminService;
}]);
As you can see I give it a name and define what services it needs and then I create an object that is my service and return it to be consumed by something else. The above syntax is TypeScript which plays very nice with Angular as that is what the Angular team uses.

Maintain Angular $scope on navigation

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);
});
});

Integrating the MobileServiceClient with AngularJS

I'm trying to use the WindowsAzure.MobileServiceClient within Angular to do single sign on and CRUD operations. Being an Angular noob, I'm trying to figure out the best way to do this:
Instantiate it in the $rootScope in .run and call the functions from there?
Create a service or factory and make the instantiation of the MobileServiceClient and all of the function calls in that? Would the currentUser and other information get lost when the service/factory isn't being used?
Just spool up MobileServiceClient in the controllers that need it? Seems to me if I do it that way, currentUser info would get lost?
I've tried using some of the above methods but I'm running into some problems:
Calling the login method as shown in the Azure docs sometimes works, other times it doesn't show a popup window to the authentication provider like it should. I am logged off of the authentication provider so a popup window should be shown but isn't,
No matter what I try to do, the MobileServiceClient currentUser is coming back as null, even when the popup was shown and I correctly entered my credentials.
Any ideas of what I can do to make this work smoothly? Any examples I can follow somewhere? The documentation seems sketchy.
I'm using Yeoman and the angular generator along with Grunt to do my work, if it makes any difference.
I was able to figure it out. I created a new service to handle all of the mobile services code. Since the methods from the client are async, I'm using callbacks in the methods. I also use the cookie store to save the user's credential object (currentUser) and pull it out again when needed. currentUser seems to lose the user credential object between calls, so I store it in a cookie and push it into the client when it has lost it.
'use strict';
angular.module('{appName}')
.factory('AzureMobileClient', ['$cookieStore', function ($cookieStore) {
var azureMobileClient = {};
azureMobileClient.isLoggedIn = false;
azureMobileClient.azureError = "";
azureMobileClient.azureMSC = new WindowsAzure.MobileServiceClient('{app URL from Azure}', '{app key from Azure}');
azureMobileClient.login = function(callback, socialMediaService) {
azureMobileClient.azureMSC.login(socialMediaService).then(function(user) {
azureMobileClient.isLoggedIn = user != null;
$cookieStore.put("azureUser", user);
callback(azureMobileClient.isLoggedIn);
}
, function(error){
alert(error);
azureMobileClient.azureError = error;
});
};
azureMobileClient.logout = function() {
azureMobileClient.getUser();
azureMobileClient.azureMSC.logout();
$cookieStore.remove("azureUser");
};
azureMobileClient.getStuff = function(callback) {
azureMobileClient.getUser();
var stuffTable = azureMobileClient.azureMSC.getTable("stuff");
stuffTable.read().then(function(items) {
callback(items);
});
};
azureMobileClient.addStuff = function(scope) {
azureMobileClient.getUser();
var stuffTable = azureMobileClient.azureMSC.getTable("stuff");
stuffTable.insert({ stuffname: scope.stuffname });
};
azureMobileClient.getUser = function() {
if (azureMobileClient.azureMSC.currentUser === null)
{
azureMobileClient.azureMSC.currentUser = $cookieStore.get("azureUser");
}
};
return azureMobileClient;
}]);
In the controller that does the login and logout, I do this:
'use strict';
angular.module('{appName}')
.controller('MainCtrl', function ($scope, $window, AzureMobileClient) {
$scope.authenticate = function (socialService) {
AzureMobileClient.login(function(isLoggedIn) {
if (isLoggedIn)
{
$window.location.href = "/#/play";
}
}, socialService);
};
$scope.signOut = function() {
AzureMobileClient.logout();
}
});
The view for the login/logout controller looks like this:
<button ng-click="signOut()">Sign Out</button>
<div class="span4">
<img src="images/facebooklogin.png" ng-click="authenticate('Facebook')" />
<img src="images/twitterlogin.png" ng-click="authenticate('Twitter')" />
<img src="images/googlelogin.png" ng-click="authenticate('Google')" />
</div>
And finally in a controller that gets data, I do this:
'use strict';
angular.module('{appName}')
.controller('StuffCtrl', ['$scope', '$window', 'AzureMobileClient', function ($scope, $window, AzureMobileClient) {
AzureMobileClient.getStuff(function(items) {
if (items.length == 0)
{
$window.location.href = "/#/stuff/new";
}
else
{
$scope.$apply($scope.items = items);
}
});
}]);
Just for someone who search ready-to-use solution https://github.com/TerryMooreII/angular-azure-mobile-service
This is angularjs service which implement below methods:
Azureservice.query(tableName, parameters, withFilterFn)
Azureservice.insert(tableName, obj, withFilterFn)
Azureservice.update(tableName, obj, withFilterFn)
Azureservice.del(tableName, obj, withFilterFn)
Azureservice.getAll(tableName, withFilterFn)
Azureservice.getById(tableName, id, withFilterFn)
Azureservice.read(tableName, parameters, withFilterFn)
Azureservice.login(oauthProvider, token)
Azureservice.logout()
Azureservice.setCurrentUser(userObj)
Azureservice.getCurrentUser()
Azureservice.isLoggedIn()
Azureservice.invokeApi()
Just add 'azure-mobile-service.module' in your dependency list and configure api keys:
angular.module('your-module-name').constant('AzureMobileServiceClient', {
API_URL : 'https://<your-api-url>.azure-mobile.net/',
API_KEY : '<your-api-key>',
});
and then:
angular.module.('your-module-name').controller('MainController', function ($scope, Azureservice) {
$scope.loginAction = function () {
Azureservice.login('facebook').then(function () {
console.log('login successful');
}).catch(function () {
console.log('login error');
}
}
}

Resources