AngularJS authentication service + watcher - angularjs

I am making authentication for my app and I've been struggling with getting a service to work properly. I have simplified the code.
The service:
App.factory('AuthenticationService', function() {
var isLoggedIn = false;
return {
isLoggedIn : function() {
return isLoggedIn;
}
}
});
MainController:
App.controller('MainController', [
'$scope',
'AuthenticationService',
function( $scope, AuthenticationService ) {
$scope.isLoggedIn = AuthenticationService.isLoggedIn();
$scope.$watch('AuthenticationService.isLoggedIn()', function (newVal, oldVal) {
console.log("Detected change in service.");
if( newVal !== oldVal ) {
$scope.isLoggedIn = newVal;
}
});
}]);
LoginController:
// Login HTTP request successful, returns a positive answer
// Now I want to change the variable in the service to True
AuthenticationService.isLoggedIn = true;
Main problem: The way the service variable is modified from LoginController right now - it is not reflected in the watcher and I am not sure if it even changes the variable in the service. What is wrong there?
The goal: Instant change in the view after a successful login (ng-show).
Any other constructive criticism towards the code is welcome.

There are several ways.
1) Use broadcast events
2) Create a scope for your service:
App.factory('AuthenticationService', function($rootScope) {
var isLoggedIn = false;
var AuthenticationService = $rootScope.$new();
return AuthenticationService;
});
3) You could watch a property:
App.factory('AuthenticationService', function() {
return {
isLoggedIn : false
}
});
// In your controller:
$scope.$watch('AuthenticationService.isLoggedIn', function(){...
4) You could write a custom watcher:
App.factory('AuthenticationService', function() {
var isLoggedIn = false;
return {
isLoggedIn : function() {
return isLoggedIn;
}
}
});
// In your controller
$scope.$watch(function(){return AuthenticationService.isLoggedIn()}, function(){...

Related

How to inject multiple angular services using $inject.get

Im having problem using $inject.get in angular js..
Let say i have angular services like this
app.service("serviceOne", function() {
this.dialogAlert = function() {
return 'Message One'
};
});
app.service("serviceTwo", function() {
this.dialogAlert = function() {
return 'Message Two'
};
});
app.service("serviceThree", function() {
this.dialogAlert = function() {
return 'Message Three'
};
});
And using the factory to dynamically call dialogAlert()
app.factory("alertService", function($window, $injector) {
if ($window.servicesOne) {
return $injector.get("serviceOne");
} else {
return $injector.get(["serviceTwo", "serviceThree"]);
}
});
With this kind of codes, it gives me "unknown provider".
Or is there any alternative solution for this?
Thanks guys.
injector.get takes only one service name as argument, array is not supported, you may want to do return array of service instances by doing return ["serviceTwo", "serviceThree"].map($injector.get):-
app.factory("alertService", function($window, $injector) {
var service = ["serviceOne"];
if (!$window.servicesOne) {
service = ["serviceTwo", "serviceThree"];
}
return service.map($injector.get); //To be consistent send back this as well as array
});
So with this when you inject the alertService it will return an array of dependecy(ies).
app.controller('MainCtrl', function($scope, alertService) {
// alertService will be array of dependecies.
console.log(alertService.map(function(itm){return itm.dialogAlert()}));
});
Demo
or return with a map:-
app.factory("alertService", function($window, $injector) {
var service = ["serviceOne"],
serviceObjs = {};
if (!$window.servicesOne) {
service = ["serviceTwo", "serviceThree"];
}
angular.forEach(service, function(itm){
serviceObjs[itm] = $injector.get(itm);
});
return serviceObjs;
});

Initiate a service and inject it to all controllers

I'm using Facebook connect to login my clients.
I want to know if the user is logged in or not.
For that i use a service that checks the user's status.
My Service:
angular.module('angularFacebbokApp')
.service('myService', function myService($q, Facebook) {
return {
getFacebookStatus: function() {
var deferral = $q.defer();
deferral.resolve(Facebook.getLoginStatus(function(response) {
console.log(response);
status: response.status;
}));
return deferral.promise;
}
}
});
I use a promise to get the results and then i use the $q.when() to do additional stuff.
angular.module('angularFacebbokApp')
.controller('MainCtrl', function ($scope, $q, myService) {
console.log(myService);
$q.when(myService.getFacebookStatus())
.then(function(results) {
$scope.test = results.status;
});
});
My problem is that i need to use the $q.when in every controller.
Is there a way to get around it? So i can just inject the status to the controller?
I understand i can use the resolve if i use routes, but i don't find it the best solution.
There is no need to use $q.defer() and $q.when() at all, since the Facebook.getLoginStatus() already return a promise.
Your service could be simpified like this:
.service('myService', function myService(Facebook) {
return {
getFacebookStatus: function() {
return Facebook.getLoginStatus();
}
}
});
And in your controller:
.controller('MainCtrl', function ($scope, myService) {
myService.getFacebookStatus().then(function(results) {
$scope.test = results.status;
});
});
Hope this helps.
As services in angularjs are singleton you can create new var status to cache facebook response. After that before you make new call to Facebook from your controller you can check if user is logged in or not checking myService.status
SERVICE
angular.module('angularFacebbokApp')
.service('myService', function myService($q, Facebook) {
var _status = {};
function _getFacebookStatus() {
var deferral = $q.defer();
deferral.resolve(Facebook.getLoginStatus(function(response) {
console.log(response);
_status = response.status;
}));
return deferral.promise;
}
return {
status: _status,
getFacebookStatus: _getFacebookStatus
}
});
CONTROLLER
angular.module('angularFacebbokApp')
.controller('MainCtrl', function ($scope, $q, myService) {
console.log(myService);
//not sure how do exactly check if user is logged
if (!myService.status.islogged )
{
$q.when(myService.getFacebookStatus())
.then(function(results) {
$scope.test = results.status;
});
}
//user is logged in
else
{
$scope.test = myService.status;
}
});

Why I can't access $rootScope value from controller?

I have a variable set to true in the $rootScope. This variable is named loggedIn.
I created a NavController to control the display of my main menu links, and as you can see I injected the $rootScope properly.
appControllers.controller("NavController", function($rootScope, $scope) {
console.log($rootScope); //We can see that loggedIn is true
console.log($rootScope.loggedIn); //Outputs false
//Not working as expected
if($rootScope.loggedIn === true){
$scope.showHomeMenu = false;
$scope.showGameMenu = true;
$scope.currentHome = '/lobby';
}
else {
$scope.showHomeMenu = true;
$scope.showGameMenu = false;
$scope.currentHome = '/login';
}
});
Oddly, this doesn't work because $rootScope.loggedIn is evaluated to false, even though it's value was set to true. This is the output I get from the console.
As you can see from the $rootScope output, loggedIn should be true. But it's evaluated to false in the controller.
I guess I'm doing something really dumb here, but I can't figure out why!!
Edit 2
#rvignacio pointed out something interesting. The object $rootScope I see is the state of the object at the time of the expansion, not the state at the moment when I call log().
(which was predictable...)
I guess the issue is deeper than I thought! I'll have to dig to find the bug!
Edit
I set loggedIn in run().
app.config(function($routeProvider, $httpProvider) {
$routeProvider.
when('/', {
...
}).
...
});
var interceptor = ['$rootScope', '$q',
function(scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
var deferred = $q.defer();
var req = {
config: response.config,
deferred: deferred
};
scope.$broadcast('event:loginRequired');
return deferred.promise;
}
// otherwise
return $q.reject(response);
}
return function(promise) {
return promise.then(success, error);
};
}
];
$httpProvider.responseInterceptors.push(interceptor);
}).run(['$rootScope', '$http', '$location',
function(scope, $http, $location) {
/**
* Holds page you were on when 401 occured.
* This is good because, for example:
* User goes to protected content page, for example in a bookmark.
* 401 triggers relog, this will send them back where they wanted to go in the first place.
*/
scope.pageWhen401 = "";
scope.loggedIn = false;
scope.logout = function() {
$http.get('backend/logout.php').success(function(data) {
scope.loggedIn = false;
scope.$broadcast('event:doCheckLogin');
}).error(function(data) {
scope.$broadcast('event:doCheckLogin');
});
};
scope.$on('event:loginRequired', function() {
//Only redirect if we aren't on restricted pages
if ($location.path() == "/signup" ||
$location.path() == "/login" ||
$location.path() == "/contact" ||
$location.path() == "/about")
return;
//go to the login page
$location.path('/login').replace();
});
/**
* On 'event:loginConfirmed', return to the page.
*/
scope.$on('event:loginConfirmed', function() {
//*********************
//THIS IS WHERE I SET loggedIN
//*********************
scope.loggedIn = true;
console.log("Login confirmed!");
$location.path('/lobby').replace();
});
/**
* On 'logoutRequest' invoke logout on the server and broadcast 'event:loginRequired'.
*/
scope.$on('event:logoutRequest', function() {
scope.logout();
});
scope.$on("$locationChangeSuccess", function(event) {
//event.preventDefault();
ping();
});
scope.$on('event:doCheckLogin', function() {
ping();
});
/**
* Ping server to figure out if user is already logged in.
*/
function ping() {
$http.get('backend/checksession.php').success(function() {
scope.$broadcast('event:loginConfirmed');
}); //If it fails the interceptor will automatically redirect you.
}
//Finally check the logged in state on every load
ping();
}
]);
You guys were right. I'm using async calls to set loggedIn to true, so it was all a question of timing.
Welcome to the world of aynschronism dear me.
$watch-ing loggedIn solved the issue.
appControllers.controller("NavController", function($rootScope, $scope) {
$scope.showHomeMenu = true;
$scope.showGameMenu = false;
$scope.currentHome = '/login';
//ADDED
$rootScope.$watch('loggedIn', function(){
console.log($rootScope.loggedIn);
if($rootScope.loggedIn === true){
$scope.showHomeMenu = false;
$scope.showGameMenu = true;
$scope.currentHome = '/lobby';
}
else {
$scope.showHomeMenu = true;
$scope.showGameMenu = false;
$scope.currentHome = '/login';
}});
});
Also I changed where I set loggedIn to false in my code to increase performance and remove visible latency when logging out. Now everything runs fast.

I need to write a simple AngularJS unit test for a controller that passes as defined

Here is the controller and I just want to test that it is defined
'use strict';
mainApp.controller('HeaderCtrl',
function ($scope, sessionSrvc, eventSrvc, $state) {
// Initially keep both SingIn and SignOut as hidden
// until it's determined if the session is alive or not.
var user = sessionSrvc.getCurrentUser();
if(user.fullName != undefined) {
$scope.signInVisible = false;
$scope.signOutVisible = true;
} else {
user.then(function(user) {
if(user != null) {
$scope.user = user;
$scope.signInVisible = false;
$scope.signOutVisible = true;
} else {
$scope.signInVisible = true;
$scope.signOutVisible = false;
}
}, function(errorId) {
// alert(errorId);
});
}
/**
* This callback is called when the the user successfully logs in
* and the signIn dialog closes.
*/
$scope.$on(eventSrvc.getSignInSucceededEvent(), function() {
$scope.user = sessionSrvc.getCurrentUser();
$scope.signInVisible = false;
$scope.signOutVisible = true;
});
/**
* Show the SignIn dialog.
*/
$scope.signIn = function() {
$('#signIn').modal('show');
};
/**
* SignOut
*
* #param account
*/
$scope.signOut = function() {
var promise = sessionSrvc.signOut();
promise.then(function(user) {
$scope.signInVisible = true;
$scope.signOutVisible = false;
window.location.replace("/");
}, function(reason) {
// alert('Failed: ' + reason);
});
};
/**
* Detect if Cookies enabled
*/
checkCookie();
function checkCookie(){
var cookieEnabled=(navigator.cookieEnabled) ? true : false
if (typeof navigator.cookieEnabled=="undefined" && !cookieEnabled){
document.cookie="testcookie";
cookieEnabled=(document.cookie.indexOf("testcookie")!=-1)? true : false;
}
return (cookieEnabled) ? true:showCookieFail();
}
function showCookieFail(){
// alert('Please enable cookies, the site will not work correctly without them');
}
/**
* Set header back to portal header (from cookbook) if any tab is clicked
*/
$('body').on('click', '.nav li a', function(){
$('.header').removeClass('cookbook');
});
/**
* links to cookbook or other docs
*
* If user clicks back button or clicks any tab, changes header back to
* service portal (from cookbook).
*/
$scope.goHome = function() {
$('.header').removeClass('cookbook');
$state.go('home.explore');
};
window.onpopstate = function() {
$('.header').removeClass('cookbook');
};
}
);
Here is the karma test I set up that passes :) Please bear with me as unit testing in Angular is new to me. How can I properly define in my test
'use strict'
describe('HeaderCtrl', function(){
var scope, sessionSrvc, eventSrvc, state;
beforeEach(module('mainApp'));
it('should have a HeaderCtrl controller', function() {
expect(mainApp.HeaderCtrl).toBe(undefined);
});
});
Controllers are instantiated on demand, when a view or a route needs one. So, your app doesn't have any instance of your controller, and your controller is not a singleton like services are. So your test doesn't make sense as is. What you can test though is the ability to create an instance of the controller:
describe('HeaderCtrl', function(){
var $controller, $scope;
beforeEach(module('mainApp'));
beforeEach(inject(function($rootScope, _$controller_) {
$scope = $rootScope.$new();
$controller = _$controller_;
}));
it('should create a HeaderCtrl controller', function() {
var controller = $controller('HeaderCtrl', {
$scope: $scope
});
expect(controller).toBeDefined();
});
});

AngularJS - Changing a template depending to a service

I am fairly new to Angular and I find it quite difficult to think the Angular way.
I have registered a SessionService which loads a user object at login. Each controller can depend on the SessionService so I can have some sort of access control.
app.factory('SessionService',['$resource', function($resource){
var service = $resource('/session/:param',{},{
'login': {
method: 'POST'
}
});
var _user = { authorized: false };
function getUser() {
return _user;
}
function authorized(){
return _user.authorized === true;
}
function unauthorized(){
return _user.authorized === false;
}
function login(newUser,resultHandler) {
service.login(
newUser,
function(res){
_user = (res.user || {});
_user.authorized = res.authorized;
if(angular.isFunction(resultHandler)) {
resultHandler(res);
}
}
);
}
return {
login: login,
authorized: authorized,
getUser: getUser
};
}]);
...this is how I access the SessionService from a controller:
app.controller('SidebarCtrl', ['$scope', 'SessionService', function($scope, SessionService) {
$scope.template = '/templates/sidebar/public.html';
if(SessionService.authorized()){
$scope.template = '/templates/sidebar/menu.html'; // <-- ???
}
}]);
...and here the html file:
<div ng-controller="SidebarCtrl">
<div ng-include src="template"></div>
</div>
Here is my question: How do I make the SidebarCtrl's template dependent on SessionService.authorize? So the sidebar shows the right content according to a user being logged in or not.
I hope this makes sense to you.
You need to $watch the SessionService.authorized(). So change the controller as:
app.controller('SidebarCtrl', ['$scope', 'SessionService', function($scope, SessionService) {
$scope.template = null;
$scope.$watch(
function() {
return SessionService.authorized();
},
function(authorized) {
if( authorized ) $scope.template = '/templates/sidebar/menu.html';
else $scope.template = '/templates/sidebar/public.html';
}
);
}]);

Resources