How can I dry up Angularfire session authorization ($onAuthStateChanged) - angularjs

Currently I have an authorization system to track user log in/out status using angularfire. The guide I'm looking at suggests using $onAuthStateChanged in every controller as so.
$rootScope.authObj.$onAuthStateChanged(function(firebaseUser) {
if (firebaseUser) {
console.log("Signed in as:", firebaseUser.uid);
});
} else {
console.log("Signed out");
}
});
Instead I've been using a $rootScope.session variable to keep track of my currently logged in user. This works great for the most part, but I can't access this session variable at the start of other controllers since the object is not instantiated at that point. Is there a clean way to access this session variable in the scope of the controller so that I don't have to make an new reference to database in each and every function (as those functions are called after the session variable is set).
To make things a bit more clear.
ref = firebase.database().ref("users/" + $rootScope.session.id + '/meetings');
list = $firebaseArray(ref);
At the top of my controller does not work as $rootScope.session.id is not set yet.
But
$scope.addMeeting = function() {
ref = firebase.database().ref("users/" + $rootScope.session.id + '/meetings');
list = $firebaseArray(ref);
list.$add({
name: $scope.meetingname,
date: firebase.database.ServerValue.TIMESTAMP
});
};
Does work as the function called on a button click which will always be after the page has already loaded, thus meaning $rootScope.session.id is set by that point.
-------------Update-----------------
I've gotten it to work using the firebases suggested methodology, but it doesn't look pretty. It involves nesting everything within a listener on firebase's Auth object and then using an if statement to ensure user object is not null.
myApp.controller('MeetingsController', ['$scope', '$rootScope', '$firebaseAuth', '$firebaseArray', function($scope, $rootScope, $firebaseAuth, $firebaseArray){
var authObj = $firebaseAuth();
authObj.$onAuthStateChanged(function(firebaseUser) {
if (firebaseUser) {
var ref = firebase.database().ref("users/" + firebaseUser.uid + '/meetings');
var meetings = $firebaseArray(ref);
$scope.addMeeting = function() {
meetings.$add({
name: $scope.meetingname,
date: firebase.database.ServerValue.TIMESTAMP
});
};
$scope.deleteMeeting = function(key) {
meetings.$remove(meetings.$getRecord(key)).then(function(ref) {
})
.catch(function(error){
console.log(error);
});
};
}
}); //onAuthStateChange
}]);

$rootScope is a bad option for this kind of storage since it gets cleaned up every time you refresh your page.
You should be lookign into ngStorage. It comes with $localStorage and $sessionStorage, take a read to see what fits better to your needs.
Then add ngStorage to your module and inject $localStorage to the controllers.
$localstorage.sessionId = id;
This will store the id in your browser.

angularfire will track the sessions for you and maintain the current user information. If you are checking for auth in the resolve of each of your states, you can pass the authenticated user into each of the controllers... there is no need for local storage since the underlying firebase SDK is handling that for you.
it might be helpful to provide additional information on the guide you are using.
this documentation here https://github.com/firebase/angularfire/blob/master/docs/guide/user-auth.md#authenticating-with-routers is old, but the pattern can still be used effectively

Create a factory to store the auth properties. Something like this.
app.factory('authService', function(){
return{
authenticated: false
};
});
and then check for it in the controllers:
$scope.authenticated = authService.authenticated;
Hope this somehow helps
P.S controller only used to display model to view and any functionalities should be moved in separate directives or services. Your controller needs a clean up in the future.

Related

angularjs rootscope user database info from firebase

I am trying to fetch user info from user's database and access it from anywhere using rootscope. I get the user's email and uid as the user signs in. However,to get the user's info from database, I need to call my database and read the info one by one and store it as a variable in rootscope to access it from everywhere.
So, my question is below :
How can I use rootscope in this example?
Is there any better way to do this?
in the below example, the console log is showing the first name, but I don't know how to rootscope it?
Thanks for help.
app.run(['$rootScope', '$state', '$stateParams', '$cookies', "$location",
function ($rootScope, $state, $stateParams, $cookies, $location) {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
$rootScope.user.uid = user.uid;
$rootScope.user.email = user.email;
return firebase.database().ref('/users/' + user.uid).once('value').then(function(snapshot) {
var firstname = snapshot.val().firstname;
console.log("first name", firstname);
});
} else {
// No user is signed in.
}
});
}]);
If you use AngularFire Always use its wrapper methods vs native firebase methods.
Do not use
firebase.auth().onAuthStateChanged
firebase.database().ref('/users/' + user.uid).once('value')
Use
$firebaseAuth().$onAuthStateChanged
var userData = $firebaseObject(firebase.database().ref('/users/' + user.uid));
Checkout the following link for full list of available methods in angularFire.
https://github.com/firebase/angularfire/blob/master/docs/reference.md
I recomend modifying your code like this.
$firebaseAuth().$onAuthStateChanged(function(user) {
if (user) {
$rootScope.user = $firebaseObject(firebase.database().ref('/users/' + user.uid));
$rootScope.user.uid = user.uid;
$rootScope.user.email = user.email;
console.log("Here you should have complete user object");
// still if you want to do something when user is loaded
/* $rootScope.user.$loaded(function(loadedUser) {
var firstname = loadedUser.firstname;
console.log("first name", firstname);
});*/
} else {
// No user is signed in.
}
});
Of course you need to include $firebaseObject and $firebaseAuth using dependency injection.
There are two approaches to fulfil your needs
Approach 1:
you can do this $rootScope.authData=user; then access it from anywhere by injecting $rootScope. but problem in this is that when you will refresh page $rootScope will be empty Check this SO Question
Approach 2:
I will prefer this approach,you can use use $getAuth() function of angularfire, $getAuth is syncronous function which will gives you current user data.
var myUser=$scope.AuthObj.$getAuth();
for more detail check this

angular display model on view after getting data from firebase

I am working on displaying collection that I got from DB in angular with firebase DB. I have those controller and service setup. in the html, I use search.users expecting it will hold all the data that I got from the DB but it won't show up. I can't figure out why. I tried few things like angular.copy or $broadcast with no luck. Can anyone help advise on this? Appreciated in advance.
.controller('SearchController', function ($scope, SearchService, logout, $location){
var search = this;
search.users = SearchService.users;
//$scope.$on('evtputUsers', function () {
// search.users = SearchService.users;
//});
})
//service for SearchService
.factory('SearchService', function ($http, $rootScope){
var userRef = new Firebase("app url");
var broadcastUsers = function () {
$rootScope.$broadcast('evtputUsers');
};
//get the user info
//insert the data to the db.
//retrieving the data
var dbUsers;
userRef.child('users').on('value', function(snapshot){
dbUsers = snapshot.val();
// angular.copy(snapshot.val(), dbUsers);
console.log('usersinDB:',dbUsers);
broadcastUsers();
}, function(err){
console.error('an error occured>>>', err);
});
return {
users: dbUsers
};
})
Rather than using $broadcast() and $on() you should use the AngularFire module.
AngularFire provides you with a set of bindings to synchronizing data in Angular.
angular.module('app', ['firebase']) // 1
.controller('SearchCtrl', SearchCtrl);
function SearchCtrl($scope, $firebaseArray) {
var userRef = new Firebase("app url")
$scope.users = $firebaseArray(userRef); // 2
console.log($scope.users.length); // 3
}
There are three important things to take note of:
You need to include AngularFire as firebase in the dependency array.
The $firebaseArray() function will automagically synchronize your user ref data into an array. When the array is updated remotely it will trigger the $digest() loop for you and keep the page refreshed.
This array is asynchronous. It won't log anything until data has populated it. So if you're logs don't show anything initially, this is because the data is still downloading over the network.

AngularJS $state.go executes before previous statement execution completes

I am using AngularJS, ui-router and $resource for RESTful webservices.
A button in html view is clicked that calls below function i.e. $scope.login(). Consequently a REST service (through $resource) is called and returns a user in case user/pass are correct,
$scope.login = function() {
myfactory.get({
email: $scope.user.email,
password: $scope.user.password
},function(user) {
accessmgr.grantAccess(user); //Line of interest - loi1
$state.go('app.dashboard-v1'); //Line of interest2 - loi2
}, function(x) {
if (x.status == 401)
$scope.authError = 'Email or Password not right';
else
$scope.authError = 'Server Error! Are you connected to internet?';
});
}
in case above successfully executes, another factory function (loi1 above) is called to store user instance in $localStorage as below;
myapp.factory('accessmgr', function($localStorage) {
//var User = {};
return {grantAccess: function(usr) {
$localStorage.user = usr;
}
}});
and ui-router $scope.go(...) takes the user to dashboard.
Problem:
Sometimes $state.go(...) executes before accessmgr.grantAccess(...) causing exceptions as the new state reads user from $localStorage that is not yet written. Reload the page manually solves the problem.
Any help would be really appreciated.
localStorage itself works in synchronous manner, but ngStorage's $localstorage doesn't. The latter is intended to be used in conjunction with scope and is tied to Angular digest cycles. My guess is that
myapp.factory('accessmgr', function($localStorage) {
return {grantAccess: function(usr) {
$localStorage.user = usr;
$localStorage.$apply();
}
}});
may help. ngStorage doesn't really shine when being used like this, probably JS generic library like store.js applies better.
A good alternative is to use model that acts as single source of truth and dumps the data to localStorage under the hood. Depending on the scale of the project, js-data-angular can be considered a solid solution for that.
ngStorage's $localStorage cannot be referred directly without using watchers (not recommended as per here, alternatively it can to be passed as a reference to hook to $scope as mentioned as recommended approach here.
For me, I was using $localStorage through a factory and I tied it to rootScope as below;
$rootScope.$storage = $localStorage;
and consequently
myapp.factory('accessmgr', function($localStorage) {
$rootScope.$storage = $localStorage;
return {
grantAccess: function(usr) {
$rootScope.$storage.user = usr;
},
getUser: function() {
return $rootScope.$storage.user;
}
}});

Is this the best way to retrieve an array with $firebase()?

I have arrays stored in Firebase, one of which I need to retrieve when a user logs in. Each user has their own array which requires authentication for read. (It would be inconvenient to switch to another data structure). Since $firebase() always returns an object, as per the docs, I'm using the orderByPriority filter. However, if I do simply
$scope.songs = $filter('orderByPriority')($firebase(myref));
that doesn't work as songs always get an empty array.
I don't understand why this happens, but what I've done to solve it is use the $firebase().$on('loaded',cb) form and applied the filter in the callback. Is this a good solution?
The drawback is that I cannot do $scope.songs.$save()
Here's my controller, including this solution:
.controller('songListController', function($scope, $rootScope, $firebase, $filter, $firebaseSimpleLogin){
var authRef = new Firebase('https://my-firebase.firebaseio.com/users'),
dataRef;
$scope.loginObj = $firebaseSimpleLogin(authRef);
$scope.songs = [];
$rootScope.$on("$firebaseSimpleLogin:login", function(event, user) {
// user authenticated with Firebase
dataRef = $firebase(authRef.child(user.id));
dataRef.$on('loaded', function(data){
$scope.songs = $filter('orderByPriority')(data);
});
});
//other controller methods go here
$scope.save = function(){
if (!$scope.loginObj.user)
{
alert('not logged in. login or join.');
return;
}
//Was hoping to do this
//$scope.songs.$save().then(function(error) {
//but having to do this instead:
dataRef.$set($scope.songs).then(function(error) {
if (error) {
alert('Data could not be saved.' + error);
} else {
alert('Data saved successfully.');
}
});
};
});
---Edit in response to Kato's answer---
This part of my app uses Firebase as a simple CRUD json store without any realtime aspects. I use $set to store changes, so I think I'm okay to use arrays. (I'm using jQueryUI's Sortable so that an HTML UL can be re-ordered with drag and drop, which seems to need an array).
I don't need realtime synchronisation with the server for this part of the app. I have a save button, which triggers the use of the $scope.save method above.
The problem with the approach above is that orderByPriority makes a single copy of the data. It's empty because $firebase hasn't finished retrieving results from the server yet.
If you were to wait for the loaded event, it would contain data:
var data = $firebase(myref);
data.$on('loaded', function() {
$scope.songs = $filter('orderByPriority')(data);
});
However, it's still not going to be synchronized. You'll need to watch for changes and update it after each change event (this happens automagically when you use orderByPriority as part of the DOM/view).
var data = $firebase(myref);
data.$on('change', function() {
$scope.songs = $filter('orderByPriority')(data);
});
Note that the 0.8 release will have a $asArray() which will work closer to what you want here. Additionally, you should avoid arrays most of the time.

Angularjs should I $rootScope data returned from firebase?

Let say I want to retrieve user info from firebase,
and this user info will be displayed in several routes/controllers
Should I $rootScope the returned user info?
or
Call below code in each controller?
firebaseAuth.firebaseRef.child('/people/' + user.id).on('value', function(snapshot) {
$scope.user = snapshot.val();
})
UPDATE
I have a following service with a getUserInfo() function then what is the best way
to use it in several controllers?
calling firebaseAuth.getUserInfo().then() in each controller?
If the user data I have to use in several controller. Why don't I set it $rootScope?
So I don't need to call it again and again in different controllers.
myapp.service('firebaseAuth', ['$rootScope', 'angularFire', function($rootScope, angularFire) {
this.firebaseRef = new Firebase("https://test.firebaseio.com");
this.getUserInfo = function(id) {
var userRef = this.firebaseRef.child('/human/' + id);
var promise = angularFire(userRef, $rootScope, 'user', {});
return promise;
}
});
The point of AngularFire is to keep your javascript data model in sync with Firebase at all times. You don't want to create a new AngularFire promise every time you need to fetch data. You just initialize AngularFire once, and your local data will always be up to date.
myapp.service('firebaseAuth', ['angularFireCollection', function(angularFireCollection) {
this.firebaseRef = new Firebase("https://test.firebaseio.com");
this.initUserInfo = function(id) {
if (!this.userRef) {
this.userRef = this.firebaseRef.child('/human/' + id);
this.userInfo = angularFireCollection(this.userRef);
}
else {
// already initialized
}
}
}]);
Remember that all properties of your service (i.e. everything you assign using the this keyword) are accessible from controllers injected with this service. So you can do things like console.log(firebaseAuth.userInfo) or firebaseAuth.userRef.on('value', function(snap) { ... });
Also, you may eventually want to use the FirebaseAuthClient for your user authentication.
I would recommend creating a service to perform the authentication and store the user data. Then you can inject the service into any controller that needs access to the user.

Resources