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.
Related
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.
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 try to do some databindings between a controller and a service.
I just want my variable userIsConnected, which is into my controller, has to change as soon as the function getUserIsConnected returns an other value.
Maybe I have to add an attribute userIsConnected into my service?
Here the example :
app.service('UserService', function($http){
var user = null;
this.getUser = function (){
return user;
};
this.setUser = function (user){
user = user;
};
this.getUserIsConnected = function (){
if(user)
return true;
else
return false;
};
});
app.controller("NavBarCtrl", function($scope, $http, $location, $rootScope, UserService) {
$scope.signInIsDisabled = true;
$scope.userIsConnected = UserService.getUserIsConnected();
});
Thanks for your help
The problem with this:
$scope.userIsConnected = UserService.getUserIsConnected();
...is that $scope.userIsConnected becomes whatever getUserIsConnected() outputs in the moment the Controller is initialized. If you want it to continue updating, you should try something like this instead:
$scope.userIsConnected = UserService.getUserIsConnected;
And then in your view, you would use {{userIsConnected()}}.
HOWEVER, from my experience, this sort of watcher is very expensive on rendering. Because when the view is rendered, it will actually run userIsConnected() 10 times before finally giving-up (because in AngularJS, the stack overflow limit is capped at 10).
I used that method in my applications when I first started using AngularJS. But now, I have a directive called "myDynamicMenu", and it detects when a route event has fired using $routeChangeSuccess (ie. when user changes page from login to dashboard, or from dashboard to logout to home).
At this event, my menu directive will check the user authentication state (logged-in or logged-out), and it will then display the proper menu (ie. the user menu [dashboard, my settings, logout], or the guest menu [login, register]). This method is much faster at rendering.
Edit: Example directive:
app.directive('myDynamicMenu', function ($rootScope, UserService) {
return {
restrict: 'E',
templateUrl: 'my-dynamic-menu.html',
link: function (scope) {
$rootScope.$on('$routeChangeSuccess', updateMenuWhenRouteChanges);
////////////////////////////////////////////////////////////
function updateMenuWhenRouteChanges () {
scope.isLoggedIn = UserService.getUserIsConnected() ? true : false;
}
}
};
});
Your template would be something like:
<div id="guest-menu" ng-show="!isLoggedIn">
I am a logged-out guest!
</div>
<div id="member-menu" ng-show="isLoggedIn">
I am a logged-in member!
</div>
And drop the menu into your layout using <my-dynamic-menu></my-dynamic-menu>.
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;
});
// .....
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.