How to wait for the Meteor.users collection gets ready in a library?

I’m using the profile field in Meteor.users system defined collection to store information about the last message user read on every communication channel with the following structure:

profile : {
    lastMsgRead : [
        {channelId: 'YYEBNEI7894K', messageCreatedAt: '14578970667"}
        {channelId: 'GERYUE789774', messageCreatedAt: '14578999845"}
    ]
}

I discovered that reading lastMsgRead field fails because of on the client the array in still empty at the time of read. I correctly publish this field to the client through:

Meteor.publish(null, function() {
    return Meteor.users.find({}, {fields: {_id: 1, username: 1, emails: 1, profile :1}});

and I read its value from a client library located in the lib directory, in this way:

var chHistory = Meteor.user().profile.lastMsgRead;

Debugging my code it looks like the changes I make to the profile field have not been propagated to all the client at the time I read them. So I need to wait for the subscription Meteor.users gets ready, but I don’t have its handle ─ you get this automatically from the framework.

How can I wait for Meteor.users subscription gets ready?

I published the same question on stackoverflow at http://stackoverflow.com/questions/30285764/how-to-wait-on-meteor-users-when-changing-profile-field

You can directly access it instead of using a variable. This will make it reactive and will rerun it each time it is changed. You could also use a session variable.

That’s not possible to know when subscriptions from universal publications get ready (see http://stackoverflow.com/questions/19890817/when-is-the-null-publish-ready).

For your example with Meteor.user(), you can check this package I published: https://github.com/gwendall/meteor-ground-user

1 Like

Hey @gwendall,

Does your package work if I need to wait on profile.lastMsgRead field of users collection?
I have seen you check on userId() in your package that is always valued in my case. I make my changes to a subfield of profile so I need to wait for the collecion propagation to all the clients.

I’m wondering if in my case it might be easier to create a separate custom collection to store user’s profile data and use a handle.ready() function to wait in my lib.
What do you think?

Are you sure you need a universal publication? Can’t you do a standard publication and a standard subscribe? This would allow you to check the ready state.

Also, you should avoid using the profile field of Meteor.users (see here).

1 Like

Yes @Steve, that’s exactly my point ─ see my last post. I would do better rewriting my code avoiding Meteor.users and then go with Meteor.subscribe(‘profile’). I’ve seen the deprecation proposal for users profile you mention in your post and you are right. I’m going to abandon this approach which, by the way, made me wasting 24 hours of coding.

Why avoiding Meteor.users? It seem there is something I am missing here :- ) Can’t you just do:

// Server
Meteor.publish('lastMsgRead', function(userId) {
    return Meteor.users.find(userId, { fields: {'profile.lastMsgRead': 1 } });
});

// Client
var subs = Meteor.subscribe('lastMsgRead', Meteor.userId());
Meteor.autorun(function() {
  if (subs.ready()) {
    ...
  }  
});
2 Likes

@Steve Makes sense to use a standard publication here indeed. However I passing the userId as a publish function parameter is quite dangerous. Your code should be:

Meteor.publish('lastMsgRead', function() {
    return Meteor.users.find({ _id: this.userId }, { fields: { 'profile.lastMsgRead': 1 } });
});
2 Likes

@gwendall, you are right of course (not sure about “dangerous”, but “unnecessary” for sure).

1 Like

@gwendall that’s exactly what I did. Why is it dangerous passing the userId as a publish function parameter? We are on a server module.

@Steve Indeed, not dangerous for this specific case.

@massimosgrelli Anyone could fetch data from the server about any other user by calling Meteor.subscribe("lastMsgRead", anyUserId). Not a big deal for the field you are publishing, but something to keep in mind if you need to send down sensitive data.

I’ve answered your question on stack overflow.

1 Like

Still having problem with Meteor.users collection on the client. I solved the previous issue about storing and retrieving user profile information creating a completely different collection named profiles where I store all the user settings I need. I couldn’t find a way to solve the problem using your suggestions and then keep all of user setting within the field profile of users universal collection.

The problem is that in the onCreated of my template container I have the following situation:

a) Meteor.userId() contains the correct userId
b) Meteor.user() is undefined, so not valued
c) if I try to reach the profile field through Meteor.users and Meteor.userId(), I find it undefined.

In order to find a workaround I tried to create a publication around users collection ─ as some of you suggested:

Meteor.publish('user-profile', function() { return Meteor.users.find({_id : this.userId}, {fields : {profile : 1}}); });
and then I put this collection on wait in the router code, like this:

waitOn: function () { return [Meteor.subscribe('user-profile')]; }

Well, same result. No way to get the profile field valued within onCreated callback.
In my understanding it is supposed to work, but it doesn’t

One issue I have had with the user document was, Meteor.userId() would correctly return the userID, however Meteor.user() would return undefined. This only happened on the first load of the app, and only 10% of the time, I debugged it by emulating a slow connection where it seemed to happen more often. I am guessing there is a small point in time when the Meteor app recognises the userId, but hasn’t sync’d the user document locally.

Even running Meteor.users.findOne(Meteor.userId()) would not return the user doc. So all sorts of weird stuff happened in my router where I did some basic checks for if the user was logged in, and then if the user was an admin. I couldn’t check the admin thing because the user document was not present on the client until later. Again - this is only an issue on the initial load, if I was already using the application and clicked into an admin route, it would operate perfectly normal… this issue was a source of much frustration.

The only way I overcame the issue was using arunoda’s fast render package.
It basically sends the user doc down the line with the initial load.

Hey @cstrat,
Pretty much the same behavior I got in my application. The question is why a standard publication on the profile field doesn’t fix it. My guess is that either something is broken in universal publications or router package ─ where I put the waiton ─ doesn’t work properly.
Unfortunately arunoda’s fast render package is no more compatible with Meteor 0.9.0 or later versions ─ https://atmospherejs.com/arunoda/fast-render

That’s very sad and I can’t figure out a solution keep using users universal package. The only option left is to move every profile information in a separate collection.
Is the Meteor team aware of this bad behavior?

Suggestions on possible workarounds are very welcome.

Sorry I should have linked you:

https://atmospherejs.com/meteorhacks/fast-render

This works for me, with the only difference that I publish not only profile but some other fields as well.

At this point, I would try the following:

  • add some other fields in the Meteor.users collection to see if it helps.
  • see if the problem lies in Iron Router, by calling Meteor.subscribe from onCreated function instead of from waitOn and check Meteor.user() in the onReady callback.

I second that. I haven’t actually looked any further after solving the issue in this way. It’s a no-brainer then. Custom publication of current user’s data, then subscribe to it on all routes with fastrender – and done.

I’m not sure why fast-render is being reported as incompatible because it definitely is. Checking the package.js on github shows that its compatible with v0.9.3 and later.

To solve you’re issue all you should need to do is install fast-render and then create a null publication. fast-render automatically publishes all of the null publications with the first page load making all necessary data to render the page available.

Mpublish(null, function() {
    return Meteor.users.find({_id : this.userId}, {fields : {profile : 1}});
}, {is_auto:true});
1 Like

Didn’t tried yet. My workaround for now is to move every user profile data outside users universal collection.
I’ll give it a try soon.