How to bypass unique ID and reference child nodes - firebase

My firbase database looks like this:
app
users
-gn4t9u4ut304u9g4
email
uid
How do I reference email and uid? When I try this:
$rootScope.dashtype.child('users').orderByChild('uid').equalTo($rootScope.auth.uid).on('value', function(snapshot){
$rootScope.user = snapshot.val();
console.log($rootScope.user);
})
I get the correct object, but with the unique id as root:
Object {-JvaZVrWGvJis0AYocBa: Object}
And because this is a dynamic property, I don't know how to reference the child objects. I just want to be able to access the user fields like this: $rootScope.user.email etc.

Since you're requesting a value, you get a list of users as a result. It may only be one user, but it's still a list of one.
You will have to loop over the snapshot, to get to the child node:
$rootScope.dashtype.child('users').orderByChild('uid').equalTo($rootScope.auth.uid).on('value', function(snapshot){
snapshot.forEach(function(userSnapshot) {
$rootScope.user = userSnapshot.val();
console.log($rootScope.user);
});
});
Since there's only a single user in the list, the loop for execute just once.
You are mixing regular Firebase JavaScript with AngularFire here. This means that you will need to inform AngularJS that you updated the scope, so that it will rerender the view:
$rootScope.dashtype.child('users').orderByChild('uid').equalTo($rootScope.auth.uid).on('value', function(snapshot){
snapshot.forEach(function(userSnapshot) {
$timeout(function() {
$rootScope.user = userSnapshot.val();
console.log($rootScope.user);
});
});
});

Related

Firebase - How do I store user data so that I can easily fetch it by their email? [duplicate]

I have the following structure on my Firebase database:
I would like to search for a user by name, last name or email but as I don't have the user key in the level above I don't know how I can achieve this. I'm doing and administrator session so it wouldn't have access to the user key.
I have tried:
let usersRef = firebase.database().ref('users');
usersRef.orderByValue().on("value", function(snapshot) {
console.log(snapshot.val());
snapshot.forEach(function(data) {
console.log(data.key);
});
});
But it brings all the users on the database. Any ideas?
You can use equalTo() to find any child by value. In your case by name:
ref.child('users').orderByChild('name').equalTo('John Doe').on("value", function(snapshot) {
console.log(snapshot.val());
snapshot.forEach(function(data) {
console.log(data.key);
});
});
The purpose of orderByChild() is to define the field you want to filter/search for. equalTo() can get an string, int and boolean value.
Also can be used with auto generated keys (pushKey) too.
You can find all the documentation here
A warning to avoid unpleasant surprises: when you use orderByChild and equalTo do not forget to add an index on your data (here's the doc)
If you don't all the nods will be downloaded and filtered client side which can become very expensive if your database grows.

Meteor 1.3: How to create a client only collection containing some fields from the users db collection?

I want to publish all the users to the client (which will eventually be only the 'profile' field of all the users):
The publication on the server looks like this:
Meteor.publish('users', function users() {
return Meteor.users.find();
});
In my template I then have a subscription that looks like this:
Template.Users_show_page.onCreated(function usersShowPageCreated() {
this.subscribe('users');
});
However the 'users' variable is not available and I still have to access the users via Meteor.users, such as in the following code:
Template.Users_show_page.helpers({
users() {
return Meteor.users.find();
}
});
Why is this?
I think I need to create a client-side collection with my choice of name - i.e. 'users', and then I can access that collection.
Where do I do this and how do I make that sync with the users in the database?
MyCollection = new Meteor.Collection('foo');
Meteor.publish('myPublication', function() {
return MyCollection.find();
});
The code above doesn't create a myPublication variable anywhere. It's just the name of the publication/subscription. You can even have multiple different subscriptions over the same collection. This code returns a cursor for the foo Mongo collection, which you access via MyCollection object.
So, your code doesn't need to create a new Mongo.Collection. Just use Meteor.users because that's the Mongo.Collection object that's already linked to "users" in MongoDB.
If you really want to access the documents in a users variable, you still need to create the helper as you suggested, although it's better you just use Meteor.users instead of the helper below:
Template.template_name.helpers({
users: function(){ return Meteor.users.find() }
});

Reactive subscription on user collection

I am trying to subscribe to profdle information of a different user than the logged in user, but I am facing issues as mentioned below
I am using angular-material and my code looks like below:
//publish user info upon following user
Meteor.publish("getUserInfo", function (userId) {
return (Meteor.users.find({_id: userId}, {fields: {profile: 1}}));
});
//subscribe
$scope.$meteorSubscribe("getUserInfo", askLikeController.$root.askLike[0].userId).then(function (subscriptionHandle) {
//Second element in the userProfile array will have the profile of required user
askLikeController.$root.usersProfile = $meteor.collection(Meteor.users, false);
});
Issues:
1. In the variable askLikeController.$root.usersProfile, I am getting both the loggedIn user and the desired userinfo having userId, I was expecting userinfo of only desired userId, why is this?
2. The subscription "getUserInfo" is not reactive, and even the subscription is lost after processing few blocks of code and then in the askLikeController.$root.usersProfile I am left with only user profile of logged in user, my guess is that my subscription is being replaced by inbuilt Meteor subscription for user.
How do I solve the issues?
Regards,
Chidan
First, make sure you have removed autopublish:
> meteor remove autopublish
To get reactivity in angular-meteor you need $meteor.autorun and $scope.getReactively. Here's an example:
// we need the requested id in a scope variable
// anytime the scope var changes, $scope.getReactively will
// ... react!
$scope.reqId = askLikeController.$root.askLike[0].userId;
$meteor.autorun($scope, function() {
$scope.$meteorSubscribe('getUserInfo', $scope.getReactively('reqId')));
}).then(function(){
askLikeController.$root.usersProfile = $meteor.collection(Meteor.users, false);
})
Getting only the user you selected: NOTICE- the logged in users is always published. So you need to specify which user you want to look at on the client side, just like you did on the publish method. So, in the subscribe method:
askLikeController.$root.usersProfile = $meteor.collection(function() {
return Meteor.Users.find({_id: $scope.getReactively('reqId')})
}, false);
At this point you might be better off changing it to an object rather than a collection:
askLikeController.$root.usersProfile = $scope.$meteorObject(Meteor.Users, {_id: $scope.getReactively('reqId')});

Retrieve user email given userId

This is more or less a follow up to this question.
I am trying to display "friends", I have a list of friends I sent a request to (called sent):
{{#each sent}}
<p>{{find_user _id}}</p>
{{/each}}
Sent is generated like so:
Template.friends.sent = function () {
return Notifications.find({from: Meteor.userId(), // to, and from are now userIds and not the user like in the original question.
type: 'friendship'});
}
And a query for the count gives a number of seven. My find_user template is defined as such:
Template.friends.find_user = function (id) {
return Meteor.users.find({_id: id});
}
How can I get the email from the a user id? Doing something like:
{{(find_user _id).emails.[0].address}}
fails, with:
Expected IDENTIFIER.
So first it appears you are iterating over a cursor from a Notifications collection and then calling the find_user method on the template with the _id of a Notification record. You'll need to use the from field of the document as it's the field that contains the userId.
Next you'll want to at least rewrite your find_user method so that it doesn't take a parameter. You can access the same data from within the helper because this is set to the current data context.
Template.friends.find_user = function () {
return Meteor.users.find({_id: this.from}); //note the this.from
}
Then you should be able to access the email address with via your template helper as long as you are publishing that data for the current user.
{{from_user.emails.0.address}}
Personally I like to use Meteor's collection transforms to extend my models with prototypes that can be used just like template helpers.
//first we create our collection and add a transform option
Notifications = new Meteor.Collection("notifications", {
transform: function(document){
return new Notification(document);
}
});
//next we create our constructor
Notification = function(document){
_(this).extend(document);
};
//Then add some prototypal methods that we can use in our templates.
Notification.prototype = {
fromUser: function(){
return Meteor.users.findOne(this.from);
}
};
Now we can use this in our templates like this:
{{fromUser.emails.0.address}}
We can also take this one really great step farther by using the users _transform property to set a function that transforms user documents as well and then add methods to them as well.
//transform each user document into a new User instance
Meteor.users._transform = function(document){
return new User(document);
};
//User constructor
User = function(document){
_(this).extend(document);
};
//and finally the User prototype with methods
User.prototype = {
defaultEmail: function(){
return this.emails && this.emails[0].address;
}
};
Now as a final result you can use it like this:
{{#each sent}
<p>{{fromUser.defaultEmail}}</p>
{{/each}}

Prevent items in scope from writing to a different user's records

I was having success with using AngularFire in a scenario where there is one user on my application.
Now that I have authentication up and running, I'm noticing that assigning items to $scope.items is catastrophic when switching users, mainly due to the $scope failing to update correctly.
Reading directly from the docs...
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items');
angularFire(ref, $scope, 'items');
I need these to be only the items of the currently authorized user. So currently, I do this (if there's a better way, don't hesitate to tell me!)
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/userId');
angularFire(ref, $scope, 'items');
I generate userId using auth.provider and auth.id, btw. Now that my items are namespaced in (let's say) user1
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/[user1id]');
angularFire(ref, $scope, 'items');
I add items to $scope.items
$scope.create = function(item) {
$scope.items.push(item)
/* Pretend the user adds these from the interface.
[
{ name: 'eenie' },
{ name: 'meenie' },
{ name: 'miney' },
{ name: 'moe' }
]
*/
}
The problem
Now if I just log out and login as someone else, magically that user has eenie meenie miney and moe because $scope.items held the array between logout and login.
I tried to set $scope.items = [] on logout event, but that actually empties all the records. I'm pulling my hair out. This is 0.001% of what I need to do in my project and it's taking my whole weekend.
Update New method
$scope.create = function() {
$scope.selectedDevice = {
name: 'New Device',
userId: $scope.user.provider + $scope.user.id
};
return $scope.devices.push($scope.selectedDevice);
};
$scope.$on('angularFireAuth:login', function(evt, user) {
var promise, ref;
ref = new Firebase('https://mysite.firebaseio.com/users/' + (user.provider + user.id) + '/registry/');
promise = angularFire(ref, $scope, 'devices');
});
It now will accurately create items under the user's id. However, still, once you logout and log back in, those items do not get cleared from $scope.devices. Therefore, they just add themselves to data but under the newly logged in user.
Update
I did a lot of trial and error. I probably set $scope.devices to [] and moved around login events in every possible combination. What eventually worked was #hiattp's fiddle in the accepted answer.
This is a result of the implicit data binding remaining intact as you switch users. If the new user shows up and creates a new binding, it will consider the existing data to be local changes that it should assimilate (that's why you see the original user's items being added to the new user), but if you try to clear them first without releasing the binding then you are implicitly telling Firebase to delete that data from the original user's item list (also not what you want). So you need to release the data bindings when you detect the logout (or login) events as needed.
The callback in the angularFire promise provides an "unbind" method (see here and here):
var promise = angularFire(ref, $scope, 'items');
promise.then(function(unbind){
// Calling unbind() will disassociate $scope.items from Firebase
// and generally it's useful to add unbind to the $scope for future use.
});
You have a few idiosyncrasies in your code that are likely causing it not to work, and remember that unbind won't clear the local collection for you. But just so you have an idea of how it should work (and to prove it does work) here is a fiddle.
You need to unbind $scope.items on logout. The best way to do this will be to save the unbind function given to your promise in $scope:
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/[user1id]');
angularFire(ref, $scope, 'items').then(function(unbind) {
$scope.unbindItems = unbind;
});
$scope.$on('angularFireAuth:logout', function() {
$scope.unbindItems();
});

Resources