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
Related
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.
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.
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've used firebase simpleLogin with facebook in AngularJS. (Generated with angular and angularfire generators in yeoman)
How do I keep the user in a variable, $scope, $rootScope etc. correctly? The user is going to be used in another angular module.
The code looks like this:
'use strict';
angular.module('angularfire.login', ['firebase', 'angularfire.firebase'])
.run(function(simpleLogin) {
simpleLogin.init();
})
.factory('simpleLogin', function($rootScope, $firebaseSimpleLogin, firebaseRef, $timeout) {
function assertAuth() {
if( auth === null ) { throw new Error('Must call loginService.init() before using its methods'); }
}
var auth = null;
return {
init: function() {
auth = $firebaseSimpleLogin(firebaseRef());
return auth;
},
logout: function() {
assertAuth();
auth.$logout();
},
/**
* #param {string} provider
* #param {Function} [callback]
* #returns {*}
*/
login: function(provider, callback) {
assertAuth();
auth.$login(provider, {rememberMe: true}).then(function(user) {
if( callback ) {
//todo-bug https://github.com/firebase/angularFire/issues/199
$timeout(function() {
callback(null, user);
});
}
}, callback);
}
};
});
For instance $rootScope.user = user; is working, but I've been told that's not a clean method. Also, if I'm refreshing I'm stilled logged in, but the $rootscope.user becomes undefined.
Thanks
The best way is to directly assign the object returned by $firebaseSimpeLogin to $scope:
$scope.auth = $firebaseSimpleLogin(firebaseRef());
You can then reference this in your views directly. For example:
<span ng-show="auth.user">
{{auth.user.name}} | Logout
</span>
Login
Meant to be a comment for the question on Anant's answer but require 50 rep to comment:
$scopes in AngularJS are arranged in a hierarchical structure that mimics the DOM so they are nestable.
The problem is that when you reload, the authenticated user won't become available until the application has talked to the FireBase server. Thankfully, FireBase will trigger an event when this is done and you can capture that event to push your user information to the $scope or $rootScope.
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
var ref = new Firebase(FIREBASE_URL + '/users/' + authUser.uid);
var user = $firebase(ref).$asObject();
user.$loaded().then(function() {
$rootScope.currentUser = user;
});
$location.path('/meetings');
});
I personally don't have a problem with storing the current user's info on the $rootScope. It's not very much data and you would probably have to feed it into the scope on every page...and that's exactly what $rootScope is for.
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.