AnguarlJS: watch change in service variable and show/hide elements - angularjs

I have a service defined as
app.service('Auth', ['$http',function ($http) {
this.isLoggedIn = false;
this.user = null;
}]
and a controller is using it as
app.controller('AuthenticationController', ['$rootScope','$scope', '$http', '$location', 'Auth',function($rootScope,$scope, $http, $location, Auth){
$scope.login = function(){
Auth.isLoggedIn = true;
Auth.user = {
name: "Shahzad Fateh Ali",
id: 1
};
$location.path('/users');
}}]);
My DOM uses it as
<header ng-show="Auth.isLoggedIn">...</header>
and
<mainmenu ng-show="Auth.isLoggedIn"></mainmenu>
Here mainmenu is a Directive.
Now, I want to observe Auth.isLoggedIn and update the DOM based on its value.
Regards,
Shahzad Fateh Ali

You can create a $watch on a function, it doesn't have to be a string name to a property on the scope. So something like this:
$scope.$watch(function() { return Auth.isLoggedIn; }, function(value, oldValue) {
// Do something when it changes
});
But I would encourage you to use events here. So your Auth service would have a method which you call to say that the user has logged in. The Auth service should get $rootScope injected, and then $rootScope.$broadcast() an event that says that the user has authenticated. Other parts of your application can then listen to that event with $scope.$on() and take action when the user logged in.

If you're having problems with isLoggedIn not getting updated in your service, try making it an object instead of just a boolean. I have had similar issues with two-way binding a boolean variable in my service and the solution was to make it part of an object. As an example, you could create an Authentication object in your service with attributes of isLoggedIn and authenticatedUser, then in your service return this object. When you set isLoggedIn and the user you should see it updated in your service as well.

Related

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)

Accessing currentUser in Angularjs

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

Factory property is null when accessing directly from controller Angular

I'm trying to build simple loggin with angular and rest api and I've got strange problem with checking whether user is logged in login form controller.
Here is my code:
everytime I refresh my app it loads current logged user
angular.module('myApp').run(['security', function(security) {
security.requestCurrentUser();
}]);
security module is something like this (simplefied, inspired by angular-app)
angular.module('security').factory('security', ['SERVER_CONFIG', '$http', '$q', '$location', 'securityRetryQueue',
function (SERVER_CONFIG, $http, $q, $location, queue) {
var service = {
currentUser : null,
isAuthenticated : function () {
return !!service.currentUser;
},
login :
....
},
logout :
....
},
requestCurrentUser :
...
}
};
return service;
}]);
so it holds data about current user and using isAuthenticated you can find out wheter user is logged in or not
angular.module('security.login').controller('LoginFormCtrl', ['$scope', 'security', function ($scope, security) {
console.log(security)
console.log(security.currentUser)
console.log(security.isAuthenticated())
}]);
console.log(security) returns object where property user is filled with user data so method isAuthenticated returns true
but here comes the strange thing:
security.user returns null and security.isAuthenticated() returns false and I don't understand why is user null...
I need it for redirecting from loggin page when accessed and user is already logged in. I know angular app uses modal for this so it would solve my problem, but I don't want to use modal...
If anyone can explain what I'm doing wrong I would be delighted...
Thanks a lot Blažek
Your application is a bit confusing in the naming of things, which may cause some conflicts. Your module is named 'security' and your factory is named 'security', this gets confusing quickly as to which one is injected where. So try renaming things just a little to something like 'securityModule' and 'securityFactory' to distinguish.
Now, the way I normally persist data through an application is with a service structured like so:
angular.module('security').service('security', ['SERVER_CONFIG', '$http', '$q', '$location', 'securityRetryQueue',
function (SERVER_CONFIG, $http, $q, $location, queue) {
// data that is persisted
var currentUser = null;
var someOtherData = null;
// internal functions
function getCurrentUser(){ ... do stuff ... }
function doOtherStuff(){ ... do stuff ... }
// public methods // for use in controllers
return {
getCurrentUser : function(){ return currentUser },
setCurrentUser : function(param){ currentUser = param }
isAuthenticated : function () {
return !!service.currentUser;
},
login :
....
},
logout :
....
},
requestCurrentUser :
...
}
};
return service;
}]);
Now another thing to look at is you "refreshing" your application, when you hit refresh, you might be (depending on browser) destroying all the stored data locally and then you'd need to fetch it from the server on first load.

How to call controller method from service in Angular?

I wonder if I can call controller method from service.
I know that Service is singleton and I can't inject $scope to the service.
In my case I manage Google Maps in service and want to open modal Dialog when user right clicks on Polygon.
As I know, to open/create new instance of dialog, somehow Service must notify controller to do that.
This is a template with controller + method and service: Template
var myApp = angular.module('myApp', []);
function MyCtrl($scope, gridService, $timeout) {
// how to call "foo" method from service?
$scope.foo = function(){
alert('called from service');
};
}
myApp.service('gridService', ['$timeout', function ( $timeout) {
var grid = {
fetching: false,
pristine: true,
pageType: 'Edit'
}
return {
gridSetup: function () {
return grid;
},
setGridSetup: function (newGrid) {
}
}
}]);
Thanks,
The answer is simple: you don't.
The service exists to manipulate data, nothing else. And it really shouldn't care "why" it's doing what it's doing. You ask the service to do something and wait for a response.
Personally I prefer using promises to resolve async operations (i.e. to notify the controller about a state change) since it's heavily supported by many angular services like $http.
But feel free to use callbacks of you wish.
Usually you do not need to call controller from the service - in general the single service could be used by any controller so service shall know nothing about them. In most cases controller calls to the service in react to some user's action and then you need to update view from controller somehow when service did its work (get response from server, etc.). I see following general ways how to do it.
1. Use callbacks.
//controller
$scope.onButtonClick = function() {
$scope.label = "wait, loading...";
function onSuccess(result) {
$scope.label = "done! result is " + result;
}
myService.doSomeWork(param1, param2, onSuccess);
}
//service
doSomeWork: function(param1, param2, onSuccess) {
$.get({...}, onSuccess);
}
So you provide a callback for each action.
2. Subscribe on events
You may use jQuery for events subscribing/triggering
//controller
$(myService).on('update', function() {
$scope.lastUpdateTime = new Date();
});
$scope.onButtonClick = function() {
myService.doUpdate();
}
//service
doUpdate: function() {
$.get({...}, function onOk() {
$(this).trigger('update');
});
}
3. Use promises
A lot of built-in angular services return promise objects, you may use them too:
//controller
myService.doSomething(param1).then(function(result) {
$scope.result = result;
});
//service
doSomething: function(param1) {
return $http.get({...});
}
4. Share some data
An example is $resource service - for example when you call query method it returns empty array-like object that could be safely put to scope and then fills it with values when http request is done.
//controller
$scope.options = myService.options;
$scope.onClick = function() { myService.update() }
//service
options: [],
update: function() {
var self = this;
$http.get({...}).success(function(res) {
self.options.splice(0, self.options.length); //to keep same array
self.options.push.apply(self.options, res.data.options);
});
}
In all these cases services and controllers are separated, services could be used with any controller and you may easily write unit-tests on services that will not break if you change your controller/view part somehow.
A possible solution would be to have a dialog service which you can inject into the grid service. So when the user right clicks on the polygon the handler would call open on the dialog service.
Take a look at the modal service on angular ui as an example.

How do I store a current user context in AngularJS?

I have an AuthService, which logs in a user, it returns back a user json object. What I want to do is set that object and have all the changes reflected across the application (logged in/logged out state) without having to refresh the page.
How would I accomplish this with AngularJS?
The easiest way to accomplish this is by using a service. For example:
app.factory( 'AuthService', function() {
var currentUser;
return {
login: function() { ... },
logout: function() { ... },
isLoggedIn: function() { ... },
currentUser: function() { return currentUser; }
...
};
});
You can then reference this in any of your controllers. The following code watches for changes in a value from the service (by calling the function specified) and then syncs the changed values to the scope.
app.controller( 'MainCtrl', function( $scope, AuthService ) {
$scope.$watch( AuthService.isLoggedIn, function ( isLoggedIn ) {
$scope.isLoggedIn = isLoggedIn;
$scope.currentUser = AuthService.currentUser();
});
});
And then, of course, you can use that information however you see fit; e.g. in directives, in templates, etc. You can repeat this (customized to what you need to do) in your menu controllers, etc. It will all be updated automatically when you change the state on the service.
Anything more specific depends on your implementation.
I would amend the good response of Josh by adding that, as an AuthService is typically of interest of anyone (say, anyone but the login view should disappear if nobody is logged), maybe a simpler alternative would be to notify interested parties using $rootScope.$broadcast('loginStatusChanged', isLoggedIn); (1) (2), while interested parties (such as controllers) would listen using $scope.$on('loginStatusChanged', function (event, isLoggedIn) { $scope.isLoggedIn = isLoggedIn; }.
(1) $rootScope being injected as an argument of the service
(2) Note that, in the likely case of a asynchronous login operation, you'll want to notify Angular that the broadcast will change things, by including it in a $rootScope.$apply() function.
Now, speaking of keeping the user context in every/many controllers, you might not be happy listening for login changes in everyone of them, and might prefer to listen only in a topmost login controller, then adding other login-aware controllers as children/embedded controllers of this one. This way, the children controller will be able to see the inherited parent $scope properties such as your user context.

Resources