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

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.

Related

AngularJS get next $state

Hey guys I wrote this code to do a front end check (I already checked in server side). To see if user is logged or not depending in the "State" it is or path in this case, because I was using the $location first, but I moved to ui.router. Any suggestions of how to make this code work with ui.router:
app.run(function($rootScope, $state, checkLogin, User) {
$rootScope.$on("$stateChangeStart", function(event, next, current) {
checkLogin.check(function(response) {
if (response) {
var nextUrl = next.$$route.orginalPath
if (nextUrl == 'login' || nextUrl == '/') {
$state.target('panel')
}
$rootScope.isLogged = true;
} else {
$rootScope.isLogged = false;
$state.target('login')
}
});
});
});

AngularJS authentication service + watcher

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(){...

Angularjs Route Listener Doesn't Recognise Service Function

I've implemented a token based authentication system to my script. I have a simple service that checks if the user is logged on. And I use this service to check user status on every route change. Weird thing is; I can login just fine, and logout with no problems. But if I login again I get this error:
Error: AuthenticationService.isLogged is not a function
and as a result my logout function doesn't work. If I reload the page the error disappears and I can logout just fine.
Here is the log out function:
$scope.logout = function logout() {
if (AuthenticationService.isLogged) {
AuthenticationService.isLogged = false;
localStorageService.remove('token');
$location.path("/login");
}
}
And here is the route listener:
run(['AuthenticationService', '$rootScope', '$location', function(AuthenticationService, $rootScope, $location) {
$rootScope.$on("$routeChangeStart", function(event, nextRoute, currentRoute) {
if (nextRoute.access.requiredLogin && !AuthenticationService.isLogged()) {
$location.path("/login");
$scope.apply();
}
});
And the service;
module.factory('AuthenticationService', ['localStorageService', function(localStorageService) {
var auth = {
isLogged: function() {
if (localStorageService.get('token')) {
return true;
} else {
return false;
}
}
}
A factory is supposed to return an object.
module.factory('AuthenticationService', ['localStorageService', function(localStorageService) {
var auth = {
isLogged: function() {
if (localStorageService.get('token')) {
return true;
} else {
return false;
}
}
return auth;
});
This should work and a good reading for factories, services, and providers can be found at http://tylermcginnis.com/angularjs-factory-vs-service-vs-provider/.
Answer was provided in comments by Aboca. I quote;
"AuthenticationService.isLogged = false <- there you are killing off the function, so when you try AuthenticationService.isLogged() it says it is not a function because it is 'false', just get rid of that part and it will work well"
Thanks a lot!

FACTORY: get current user.id for Firebase Simple Login (Email / Password)

I am looking for a solid way to have the 'current user id' in all my controllers available.
Using: Firebase Simple Login : Email / Password Authentication
My ida: I need a 'Factory' wich I can inject into my controllers,
to have the 'current user id' always available.
I came up with this code:
app.factory('User', ['angularFire',
//Get Current UserID
function(angularFire){
console.log ('FACTORY: User');
var currentUser = {};
var ReturnStr = '';
var ref = new Firebase("https://myFIREBASE.firebaseio.com/");
var authClient = new FirebaseAuthClient(ref, function (err, user) {
if (err) {
ReturnStr = 'FACTORY: User Error: ' + err;
console.log (ReturnStr);
//var User = ReturnStr;
} else if (user) {
console.log ('FACTORY: User: Login successfully:');
console.log (user);
currentUser = user;
} else {
//console.log ('-----------User: Logged Out ---------------');
ReturnStr = 'FACTORY: Logged out: Redirect to Login';
console.log (ReturnStr);
window.location.href = "/login.php";
}
});
return currentUser;
}
]);
My simplest Controller looks like:
function ToDoCtrl($scope, User) {
$scope.MyUser = User;
$scope.MyUser.test = 'Test';
}
In HTML (angular partials) i have:
<h2>{{MyUser.id}}</h2>
<h2>{{MyUser.email}}</h2>
<h2>{{MyUser.provider}}</h2>
<h2>{{MyUser.test}}</h2>
=> id, email, provider are 'undefined'. In console I see the 'FACTORY: User: Login successfully:' with correct user - Object.
=> Asynchronous loading of data problem?
I have also experimented (without luck):
$timeout(function () {
currentUser = user;
}
Such a FACTORY would be very useful!
Thanks for a pointing me in the right direction!
Edit 1.1: Now, with $rootscope hack
=> Same effect - mycontroller is too fast - factory to slow.
app.factory('User', ['$rootScope', '$timeout', 'angularFire',
//Aktueller Benutzer auslesen
function($rootScope, $timeout, angularFire){
console.log ('FACTORY: User');
var currentUser = {};
var ReturnStr = '';
var ref = new Firebase("https://openpsychotherapy.firebaseio.com/");
var authClient = new FirebaseAuthClient(ref, function (err, user) {
if (err) {
ReturnStr = 'FACTORY: User Error: ' + err;
console.log (ReturnStr);
//var User = ReturnStr;
} else if (user) {
console.log ('FACTORY: User: Login successfully:');
//currentUser = user;
$timeout(function () {
ReturnStr = 'FACTORY: Inside timout';
console.log (ReturnStr);
currentUser = user;
console.log (currentUser);
$rootScope.myUser = user;
$rootScope.myUserID = user.id;
$rootScope.loggedIn = true;
$rootScope.$apply();
return currentUser;
});
} else {
//console.log ('-----------User: Logged Out ---------------');
ReturnStr = 'FACTORY: Logged out: Redirect to Login';
console.log (ReturnStr);
//var User = ReturnStr;
window.location.href = "/login.php";
}
});
return currentUser;
}
]);
TAHNKS for any helpful suggestions! Wonderin how others solve this!
So here is my solution to this exact problem. I am using Firebase, FirebaseAuthClient, and angularFire for my Angular app. I ran into the same situation for my login system where you cannot inject the $scope into the factory and therefore I came up with making a controller that used a factory for it's methods to retrieve, add, update, and delete things. And in the controller, I have my firebaseAuth stuff going on, the setting of the User values, and references whick I assign to the scope of that. Once the user is logged in, they are redirected to another location, at which point the app.js file takes over with a child controller when at that address location.
My login also uses localStorage, so logins will persist, you can refresh and not have to keep logging in, and you can change it to be cookies or sessionStorage easy enough.
This is going to need to be adapted for your needs specifically if you choose to use this method, it's quite complex no matter what, but this is very solid for me and I'm no longer needing to worry about firebaseAuth or angularFire stuff now that my factories are all setup for passing data back and forth. I'm just doing angularJS stuff mostly with directives. So here's my code.
NOTE: This will need modifying, and some things will be pseudo or open-ended for you to figure out for your needs.
AuthCtrl.js
'use strict';
angular.module('YOUR_APP', []).
controller('AuthCtrl', [
'$scope',
'$location',
'angularFire',
'fireFactory',
function AuthCtrl($scope, $location, angularFire, fireFactory) {
// FirebaseAuth callback
$scope.authCallback = function(error, user) {
if (error) {
console.log('error: ', error.code);
/*if (error.code === 'EXPIRED_TOKEN') {
$location.path('/');
}*/
} else if (user) {
console.log('Logged In', $scope);
// Store the auth token
localStorage.setItem('token', user.firebaseAuthToken);
$scope.isLoggedIn = true;
$scope.userId = user.id;
// Set the userRef and add user child refs once
$scope.userRef = fireFactory.firebaseRef('users').child(user.id);
$scope.userRef.once('value', function(data) {
// Set the userRef children if this is first login
var val = data.val();
var info = {
userId: user.id,
name: user.name
};
// Use snapshot value if not first login
if (val) {
info = val;
}
$scope.userRef.set(info); // set user child data once
});
$location.path('/user/' + $scope.userRef.name());
} else {
localStorage.clear();
$scope.isLoggedIn = false;
$location.path('/');
}
};
var authClient = new FirebaseAuthClient(fireFactory.firebaseRef('users'), $scope.authCallback);
$scope.login = function(provider) {
$scope.token = localStorage.getItem('token');
var options = {
'rememberMe': true
};
provider = 'twitter';
if ($scope.token) {
console.log('login with token', $scope.token);
fireFactory.firebaseRef('users').auth($scope.token, $scope.authCallback);
} else {
console.log('login with authClient');
authClient.login(provider, options);
}
};
$scope.logout = function() {
localStorage.clear();
authClient.logout();
$location.path('/');
};
}
]);
And now for the nice and simple yet quite reusable factory. You will need to set your Firebase path for your app for the baseUrl variable for it to work.
fireFactory.js
'use strict';
angular.module('YOUR_APP').
factory('fireFactory', [
function fireFactory() {
return {
firebaseRef: function(path) {
var baseUrl = 'https://YOUR_FIREBASE_PATH.firebaseio.com';
path = (path !== '') ? baseUrl + '/' + path : baseUrl;
return new Firebase(path);
}
};
}
]);
Info
You give the factory just a piece of the path reference such as 'users' which will be used as part of the full path ref to where you want to store your user data.
fireFactory.firebaseRef('users')
Once you have a reference set for a user, they won't need to be set again it will just use the existing data and .auth() to it. Additionally if there's an existing 'token' in localStorage it will use that to auth() the user too.
Otherwise, it will login() the user and pop open the Oauth windows for them to do so using whatever option you provide them.
I have spent a lot of time, many many hours days and yes even months searching for something better than this when it comes to Firebase/FirebaseAuthClient and angularFire. With the way the Firebase API and FireAuth API is, it's very annoying to make them play nicely with each other when using them with angularFire anyways. It's very frustrating but I've gotten past it finally.
If you want to check out the code for my app and see how I'm doing these things more completely, you can find it in this branch of my Webernote github repo.
Feel free to fork it, install and run it locally, or contribute to it even if you like. I could use some help myself :)
Hope this helps you!!
Here's the way I do it.
First of all I have my firebase auth service (I'm not using Angularfire) that calls off to Singly to handle logins. When the user status changes it $broadcasts an event.
p4pApp.factory('firebaseAuth', function($rootScope, singlyAuth) {
var auth = {},
FBref = new Firebase(p4pApp.FIREBASEPATH);
auth.login = function(service) {
singlyAuth.login(service);
};
auth.logout = function() {
FBref.unauth();
auth.user = null;
auth.broadcastAuthEvent();
};
auth.broadcastAuthEvent = function() {
$rootScope.$broadcast('authEvent');
};
auth.authWithToken = function(token) {
if (token !== undefined) {
FBref.auth(token, function(error, authData) {
if(!error) {
auth.user = authData.auth.account;
auth.broadcastAuthEvent();
} else {
auth.user = null;
auth.broadcastAuthEvent();
}
}, function(error) {
auth.user = null;
auth.broadcastAuthEvent();
});
}
};
return auth;
});
Then I have a 'top level' controller that looks after authorisation state.
var AuthCtrl = function($scope, firebaseAuth, singlyAuth, firebase, user) {
$scope.user = null;
$scope.logout = function() {
firebaseAuth.logout();
};
$scope.isLoggedIn = function() {
return !!$scope.user;
};
// src: Alex Vanston (https://coderwall.com/p/ngisma)
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
$scope.$on('authEvent', function() {
$scope.safeApply(function() {
$scope.user = firebaseAuth.user;
});
user.setID(firebaseAuth.user);
if (firebaseAuth.user) {
firebase.fetch(['users', firebaseAuth.user], function(results) {
if (results) {
user.setData(results);
} else {
results = {};
results.createdAt = DateTimeStamp();
}
results.lastLogin = DateTimeStamp();
firebase.set('users', firebaseAuth.user, results);
});
} else {
user.clearData();
}
});
};
Finally, I use a dedicated user service to maintain user state. (It's still in development)
p4pApp.factory('user', function() {
var userService = {}, user={};
user.data = {};
userService.setID = function(id) {
user.id = id;
};
userService.getID = function() {
return user.id;
};
userService.setData = function(data) {
user.data = data || {};
};
userService.getData = function() {
return user.data;
};
userService.clearData = function() {
user.data = {};
};
userService.setDataField = function(field, data) {
user.data[field] = data;
};
userService.clearDataField = function(field) {
delete user.data[field];
};
userService.pushData = function(key, data) {
if (typeof(key) === 'string') {
user.data[key] = data;
} else {
_.reduce(key, function(obj, child, index, list) {
obj[child] = obj[child] || {};
if (index == list.length-1) {
obj[child] = data;
}
return obj[child];
}, user.data);
}
};
userService.deleteData = function(key) {
if (typeof(key) === 'string') {
delete user.data[key];
} else {
_.reduce(key, function(obj, child, index, list) {
obj[child] = obj[child] || {};
if (index == list.length-1) {
delete obj[child];
return;
}
return obj[child];
}, user.data);
}
};
return userService;
});
This is most probably due to async nature of the call. To fix it you would have to
Inject $scope into the factory function (similar to angularFire dependency)
Use $scope.$apply() after the assigment currentUser = user;
Another solution that works for me:
account.service.js
(function() {
'use strict';
angular
.module('app.account')
.factory('Account', Account);
Account.$inject = [
'$firebaseAuth',
'$firebaseObject'
];
/* #ngInject */
function Account(
$firebaseAuth,
$firebaseObject,
) {
var firebaseRef = new Firebase('https://<<-- MY_FIREBASE -->>.firebaseio.com');
var authObj = $firebaseAuth(firebaseRef);
var service = {
userInfo: null
};
activate();
return service;
////////////////
function activate() {
// Add listeners for authentication state changes
authObj.$onAuth(function(authData) {
if (authData) {
// Load the userInfo
loadUserInfo(authData);
} else {
// Destroy the userInfo Object if one exists
if (service.userInfo) {
service.userInfo.$destroy();
service.userInfo = null;
}
}
});
}
function loadUserInfo(authData) {
var userRef = firebaseRef.child('users').child(authData.uid);
var loadedInfo = $firebaseObject(userRef);
loadedInfo.$loaded()
.then(function() {
service.userInfo = loadedInfo;
})
.catch(function(error) {
switch (error.code) {
case 'PERMISSION_DENIED':
alert('You don\'t have the permission to see that data.');
break;
default:
alert('Couldn\'t load the user info.');
}
});
}
}
})();
some-component.controller.js
(function() {
'use strict';
angular
.module('app')
.controller('SomeController', SomeController);
SomeController.$inject = [
'Account'
];
/* #ngInject */
function SomeController(
Account
) {
var vm = this;
vm.userInfo = userInfo;
////////////////
function userInfo() {
return Account.userInfo;
}
...
}
})();
some-component.html
...
<div class="user-id">
{{vm.userInfo().id}}
<div>
...

How to handle Firebase auth in single page app

I am using firebase simple login auth with angularjs and I want to create a single page app.
Before this, I have tried using service, main controller to handle it but I don't think it is good enough because I have to call FirebaseAuthClient() every time there is a route.
I also try to put FirebaseAuthClient() in angularjs .run() which initialize when app is start.
But it won't work when there is a route, I think it is because not a full page load.
Ok,
And here is what I want,
except login page, every route (pages) are required login.
A global FirebaseAuthClient() checking on every route so I don't need to call it again.
A global user which return from FirebaseAuthClient()
I'm not sure I understand. You only need to initialize FirebaseAuthClient and all login() once in your entire app. It's a singleton, and your auth credentials apply to any Firebase operations you perform.
What makes you think that this is not the case? What sorts of errors are you seeing?
Here is what I was using before moving auth over to Singly. Maybe it helps?
p4pApp.factory('firebaseAuth', function($rootScope) {
var auth = {},
FBref = new Firebase(p4pApp.FIREBASEPATH);
auth.broadcastAuthEvent = function() {
$rootScope.$broadcast('authEvent');
};
auth.client = new FirebaseAuthClient(FBref, function(error, user) {
if (error) {
} else if (user) {
auth.user = user;
auth.broadcastAuthEvent();
} else {
auth.user = null;
auth.broadcastAuthEvent();
}
});
auth.login = function() {
this.client.login('facebook');
};
auth.logout = function() {
this.client.logout();
};
return auth;
});
The AuthCtrl is common to all/most of my pages.
var AuthCtrl = function($scope, firebaseAuth) {
$scope.login = function() {
firebaseAuth.login();
};
$scope.logout = function() {
firebaseAuth.logout();
};
$scope.isLoggedIn = function() {
return !!$scope.user;
};
// src: Alex Vanston (https://coderwall.com/p/ngisma)
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
$scope.$on('authEvent', function() {
$scope.safeApply(function() {
$scope.user = firebaseAuth.user;
});
});
};
From within the AuthCtrl you can just call isLoggedIn() to see if the user is logged in or not.

Resources