I currently have the following code in my app.js config which is used on secure routes:
var checkLoggedin = function($q, $timeout, $http, $location, $rootScope){
// Initialize a new promise
var deferred = $q.defer();
// Make an AJAX call to check if the user is logged in
$http.get('/api/loggedin').success(function(user){
// Authenticated
if (user !== '0')
$rootScope.user = user; //IS THIS ADVISABLE???
$timeout(deferred.resolve, 0);
// Not Authenticated
else {
$timeout(function(){deferred.reject();}, 0);
$location.url('/login');
}
});
return deferred.promise;
};
The back end returns the user object.
I would like to be able to access the current user from different controllers and wondered whether I should make use of $rootscope (as shown above) or whether a Service would be the way to go.
Any code samples would be much appreciated if Service is the way forward.
If you only want to use your user variable in the views, reading from the rootScope is fine. However, if you in a controller want to use that user for something, you can't read it directly from the rootScope, as that variable may not yet have been set by your checkLoggedin function.
For instance, if you have code like this in a controller
.controller('NavbarCtrl', function ($scope, $rootScope) {
if ($rootScope.user) {
// do logged in stuff
}
//...
it may fail, as the promise has not yet set the user variable in the rootscope.
It's a bit annoying, but to guarantee that the variable always is set, one will need to reed it as a promise. So I would try to always access the logged in user through a service.
.service('PersonService', function ($http) {
return {
getCurrentUser: function() {
return $http.get('/api/person/me', {cache: true});
// or you can use something similar to your code above
}
}
});
Is an example. As you can see, I have enabled cache, so multiple calls to the function will not create new calls to the back end.
Then use it like this:
.controller('NavbarCtrl', function ($scope, PersonService) {
PersonService.getCurrentUser().success(function(user) {
$scope.user = user;
// or maybe do something else
$scope.showButton = user.permissionLevel > 5;
});
// .....
Related
I am trying to give access to a json file that contains config information for my project (things like rev number, project name, primary contact, etc) I created a factory that retrieves the json file using http.get, I can then pull that data into my controller but I am unable to access it from anywhere in the controller.
I did not write the factory, I found it as an answer to another person's question and it is copied almost entirely so if it not the right way to accomplish what I am trying to do please correct me.
here is the factory:
app.factory('configFactory', ["$http", function($http) {
var configFactory = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('assets/json/config.json').then(function(response) {
// The then function here is an opportunity to modify the response
console.log(response.data.config);
// The return value gets picked up by the then in the controller.
return response.data.config;
});
// Return the promise to the controller
return promise;
}
};
return configFactory;
}]);
and here is my controller:
app.controller('footerController', ['$scope', '$rootScope', 'configFactory', function footerController($scope, $rootScope, configFactory) {
var body = angular.element(window.document.body);
$scope.onChange = function(state) {
body.toggleClass('light');
};
configFactory.async().then(function(d) {
$scope.data = d;
// this console log prints out the data that I am trying to access
console.log($scope.data);
});
// this one prints out undefined
console.log($scope.data);
}]);
So essentially I have access to the data within the function used to retrieve it but not outside of that. I can solve this with rootScope but I am trying to avoid that because I think its a bandaid and not a proper solution.
Any help would be great but this is my first experience with http.get and promises and all that stuff so a detailed explanation would be very much appreciated.
[EDIT 1] The variables from the config file will need to be manipulated within the web app, so I can't use constants.
Don't assign your response data to scope variable , create a property in your factory itself and assign the response to this property in your controller when your promise gets resolved.This way you will get the value in all the other controllers.
I have updated your factory and controller like below
app.factory('configFactory', ["$http", function($http) {
var configFactory = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('assets/json/config.json').then(function(response) {
// The then function here is an opportunity to modify the response
console.log(response.data.config);
// The return value gets picked up by the then in the controller.
return response.data.config;
});
// Return the promise to the controller
return promise;
},
config:'' // new proprety added
};
return configFactory;
}]);
app.controller('footerController', ['$scope', '$rootScope', 'configFactory', function footerController($scope, $rootScope, configFactory) {
var body = angular.element(window.document.body);
$scope.onChange = function(state) {
body.toggleClass('light');
};
configFactory.async().then(function(d) {
// $scope.data = d;
configFactory.config=d;
// this console log prints out the data that I am trying to access
console.log($scope.data);
});
// this one prints out undefined
console.log($scope.data);
}]);
Have you looked into using angular constants? http://ilikekillnerds.com/2014/11/constants-values-global-variables-in-angularjs-the-right-way/ You can leverage them as global variables accessible from any controller without the ramifications of assigning the values to rootScope
I need to use angular service to create cascading drop-downs.The commented code I created for testing purpose and it is working fine. I need to create two services to call two methods from the MVC controller : GetCompanies() and GetDocTypes()
My questions are: Is my first service correct and how can I call the services from the controller?
Thank you.
/// <reference path="angular.js" />
//var myApp = angular
// .module("myApp", [])
// .controller("companyController", function ($scope, $http) {
// $http.post('CurrentSettings/GetCompanies')
// .then(function (response) {
// var response = $.parseJSON(response.data);
// $scope.currentSettings = response;
// });
// });
var myApp = angular.module("myApp", []);
myApp.service('getCompanies', function () {
$http.post('CurrentSettings/GetCompanies')
.then(function (response) {
var response = $.parseJSON(response.data);
$scope.currentSettings = response;
});
});
myApp.controller("companyController", function ($scope, getCompanies, $http) {
});
The problem with your service is two-fold:
Firstly, there is no way to call the service. You injected it fine, but now what? Think of your service as an API; it's no good just having a reference to it somewhere, you need to be able to use it. I would change to the following:
var myApp = angular.module("myApp", []);
myApp.service('getCompanies', ["$http", function($http) {
this.currentSettings = "Hello";
$http.post('CurrentSettings/GetCompanies')
.then(function(response) {
var response = $.parseJSON(response.data);
this.currentSettings = response;
});
}]);
myApp.controller("companyController", ["$scope", "getCompanies",
function($scope, getCompanies) {
$scope.currentSettings = getCompanies.currentSettings;
}]);
Note a few things:
You need to explicitly inject $http into your service.
I specify the names of the services that I'm injecting as part of an array that includes the function. This actually allows you to name the parameters anything you want, and is considered a best practice.
The service doesn't use $scope directly. Instead, it makes a field available to clients of the service. That client (the controller in this case) can then do with the value whatever it wants, including assigning it to a $scope field.
The controller reads this field from the service. It could also call any functions you specified - making the service an API, as I mentioned before.
The second problem is one of timing. Notice that I used the super-original value of "Hello" to initialize the service field.
The value you receive from the service will depend on whether or not the controller reads the value after your call to the MVC controller returns.
To fix this, the service could expose a second field to indicate that the company list is fully loaded, but that really shifts the problem around instead of fixing it.
What you need is a function that returns a promise. If the value has already been loaded, the promise resolves immediately. If not, it returns a promise that will return once the $http call is done.
Here is the modified code:
var myApp = angular.module("myApp", []);
myApp.service('companiesService', ['$http', '$q', function($http, $q) {
var currentSettings = null;
this.getList = function() {
var def = $q.defer()
if (currentSettings) {
def.resolve(currentSettings);
} else {
$http.get('CurrentSettings/GetCompanies')
.then(function(response) {
var response = response.data;
currentSettings = response;
def.resolve(currentSettings);
});
}
return def.promise;
}
}]);
myApp.controller('companyController', ['$scope', 'companiesService',
function($scope, companiesService) {
$scope.currentSettings = '';
companiesService.getList().then(function(value) {
$scope.currentSettings = value;
});
}
]);
It becomes a bit more complicated because you have to use promises, but these are the things to note:
I changed the name of the service to make it more generic. It can now offer a number of company-related features.
currentSettings is no longer added to this on the service, but instead becomes a normal (private) variable. The calling code can only read it by calling the getList function.
getList returns a promise. The promise is resolved immediately if currentSettings has been assigned. If not, it only resolves once the value is received from the web service.
The controller calls getList and assigns the value to the $scope field in the then function.
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)
I have two controllers that want the same data around the same time, but I don't want the data binded.
So I have a service to go get user data that has this method:
var getUserData: function() {
if (!angular.isDefined(this.userData)) {
return new RESTUtil.getData('userData').then(function(data) {
this.userData = data;
return this.userData
})
} else {
return this.userData
}
}
Then I have two controllers that pull in the data for different purposes:
controller('ControllerA', ['$scope', '$rootScope', 'DataService'], function($scope, $rootScope, DataService) {
var ctrlA = this;
DataService.getUserData().then(function(data) {
ctrlA.userData = data;
});
});
controller('ControllerB', ['$scope', '$rootScope', 'DataService'], function($scope, $rootScope, DataService) {
var ctrlB = this;
DataService.getUserData().then(function(data) {
ctrlB.userData = data;
});
});
So the problem with what I currently have written is both ControllerA and ControllerB hit the getUserData service before the RESTUtil call returns, so i end up getting two data calls to the service.
I thought that i'd use a $scope.$on in Controller B to listen to a broadcast from ControllerA, but that binds the data.
Basically if ctrlA.userData = this and I change ctrlB.userData = that, i don't want the two userData objects to bind, they should be instantiated.
Any suggestions on how I can either a.) write my service logic where any calls to getUserData while a REST Call is happening will wait and use the same data call, or B.) have a broadcast that doesn't bind?
I saw a similar question posted here: Have multiple calls wait on the same promise in Angular
But i'm not sure how to leverage the $http since my actual webservice call is abstracted inside the RESTUtil.getData();
You're almost there. But your service has a flaw. The first time it's called, it returns a promise. But the second time it's called (if the promise has been resolved), it doesn't return a promise anymore, but the resolved data instead.
The service should simply always return a promise, and it should initialize the promise the first time it's called:
var getUserData: function() {
if (!angular.isDefined(this.userData)) {
this.userData = RESTUtil.getData('userData');
}
return this.userData;
}
If you want to avoid each caller to receive the same object from the promise, then return a new promise each time, that is resolved with a copy of the original data:
var getUserData: function() {
if (!angular.isDefined(this.userData)) {
this.userData = RESTUtil.getData('userData');
}
return $q.when(this.userData).then(angular.copy);
}
I need a solution to alter and access the values in a session based store in angular.
I need to access it in code parts like this:
app.run(function($rootScope, $state, $window, $alert, AuthService) {
$rootScope.$on(
and in my controllers, and in my html, everywhere. I did not find a solution for this. I tried services (with ngStorage)
appServices.factory('AuthService', function ($rootScope, $localStorage){
return {
set: function(entidy, value) {
$storage = $localStorage;
$storage[entidy] = value;
}
get: function(entidy) {
if (typeof $rootScope.$storage != 'undefined') {
return $rootScope.$storage[entidy];
}
}
}
});
Writing to my service like this:
AuthService.set(email, 'test#test.de');
AuthService.set(token, '12345');
AuthService.set(auth, true);
But Getter / Setter sucks, This is not the 1990s. Also above does not work. Any ideas? Do I need a service at all to cache data in sessionStorage? Keep in mind I need to access this on the root scope (and everywhere else).
Hence $scope.$storage = $localStorage will fail.
Yes this question is ngStorage specific, but the same setup should be true for Angular's own cacheFactory.
Plunkr: http://plnkr.co/edit/GQomw4LqHCi7MJZTHZIG?p=preview
I don't think you need to assign $localStorage to your $rootScope. Whenever you need to use $localStorage, just inject it into your code:
appServices.factory('AuthService', function ($localStorage){
return {
set: function(entidy, value) {
$localStorage[entidy] = value;
},
get: function(entidy) {
return $localStorage[entidy];
}
}
});
In case you really need access the service though $rootScope, try one more step in your .run:
app.run(function($rootScope, $localStorage) {
$rootScope.storage = $localStorage;
});
http://plnkr.co/edit/IeRo3fAtQa6qcJZi1Czp?p=preview