meteor: no page updates after updating collection - meteor

I have published and subscribed successfully two collections. On client side, i put the subscribe method into an autorun function and my collections updates every time i change some session variables. My data model looks like the following:
Topics: {
_id: ObjectID,
comments: [
commentId1,
commentId2,
etc...
]
}
Comments: {
_id: ObjectID,
}
When i create a new comment, i insert the comment in the Comments collection and update the Topics collection with a $push on the array. After this i expected, that meteor re-renders my page, but this happens only if i change my topic session variable and change it back or reload the page manually... Only then meteor renews the subscription.
Why is this so? I think i missed something... But the docs gave me no clues.

I guess, the reason is that you are performing database insertion on client side, but can't tell why it's not rendering page again.
One more way you can do that is, perform insertion on server side using Meteor.call() as:
if (Meteor.isClient){
....
....
Meteor.call('addComment', 'your_new_comment');
....
}
And on the server-side:
if(Meteor.isServer){
Meteor.Methods({
'addComment' : function(data){
//your insertion code here.
Comments.insert(data);
}
});
}
The same you can do for Topics also. This will update the relevant data on server-side, and because you have already published-subscribed those data, it will be automatically published on client, even though the session variable doesn't changes.

Related

Meteor.users publish and subscribe do not match

I have removed the auto-publish package from my Meteor app and I have also created a publication on my server:
Meteor.publish("userData", function () {
return Meteor.users.find(
{_id: this.userId},
{fields: {'profile': 0}}
);
});
As you can see above I have set the profile to 0 which means I would like to exclude it. However...
On my client I have this code:
Meteor.subscribe("userData", (param) => {
console.log( Meteor.users.find(Meteor.userId()).fetch() )
})
and the output still includes the profile:
createdAt: Sat May 19 2018 11:16:25 GMT+0800 (+08) {}
emails: [{…}]
profile: {name: "Second Second"}
services: {password: {…}, resume: {…}}
username: "seconduser"
_id: "ESmRokNscFcBA9yN4"
__proto__: Object
length: 1
__proto__: Array(0)
What's the reason for this?
It is possible that some other subscription has subscribed to the profile field of the user.
You can find out if this is the case by looking at the information sent over the websocket.
Open the debugger,
find a "networking" tab,
find the websocket connection,
find the content or "frames".
There you can see which subs have been made and which updates the server publishes to a collection. See what it looks like without your sub; maybe the user doc is published already.
You see the profile field on the client since you have already edited and thus enabled this special field of the User object while creating or updating user. In order to secure this object from client-side modifications you can deny all writes from the client with the following server-side code.
// Deny all client-side updates to user documents
Meteor.users.deny({
update () { return true; },
});
So, even though the profile field is be available on the client within the Meteor.user() object, no modification can be made by the client.
If it is a custom data that you publish than you can control its exposition in your way. For example, let's assume that we introduce a new field customProfile into the user object, than with the following code, the customProfile will not be visible to the client.
Meteor.publish("userData", function () {
console.log('publishing userData with id', this.userId);
return Meteor.users.find(
{_id: this.userId},
{fields: {'customProfile': 0}}
);
});
You may find more information in the guide.
First of all, make sure whether you want to use Meteor.subscribe() or you want to use this.subscribe(). There is a lot of difference between them.
Meteor.subscribe() will keep subscription undestroyed when you change between screens/routes/UI.
this.subscribe() will have the scope of subscription till the life of Template exists. When you switch to other routes/path/UI, the subscription will be destroyed. This is used in a specific case when you have multiple kinds of subscription among consecutive transitions of the screen and problem occurs for unwanted data shown in UI despite filtering in Collection Query.
For more insight, click here.
Comming to your exact question, well when Meteor knows that you are a valid and logged in user, it sends entire Users specific collection fields _id, emails, profile, username on UI. So, it is recommended that you put only the required data into the User collection. Whether or not you make special kind of subscription to self-data, you will always be able to access your own data on UI, even on production build. You can check by putting console.log(Meteor.user()); in chrome console. This is how Meteor.user() is made, whether you like it or not. It was assumed by MDG (Meteor Development Group) that when the user has logged in, a user can fully access his/her own data at UI as it is safe and valid.
see below image for reference,

How to Reproduce Meteor.user() Client-Server effect for a different Collection?

Just how Meteor.user() method is available on Client & Server for the "current user" I would love to reproduce this kind of functionality for different custom collections. For example, my app uses a "clouds" collection as a type of room for a group of users to be in. Obviously there are various cloud instances and I don't always want to be passing the cloudId into every single meteor method. Ideally I could have like a Meteor.cloud() function that would give me the current cloud on the client and server.
My thoughts on approaching this:
What I have been doing thus far is piggy-backing off of Meteor.user() by storing a currentCloudId property inside the user profile and setting that on a route beforeAction. However this limits the user to only being in 1 cloud at a time.
Using the Meteor.connection ID somehow to keep a map of connectionIds to cloudIds. This would work great in theory....however it seems that Meteor connection IDs cannot be heavily relied on as they might change during reconnects or other random scenarios. Also you would have to then "manange" that collection of "cloudConnections" and remove old stale ones and such.
Im using Iron Router....and if it were possible to get the current route data on the server that would also solve my problem but I am not sure how to access that on the server?
--- Basically I would love for a simple straight forward way to mimic Meteor.user() behavior for other collections.
Thanks for all your help :)
You can just create a function inside /lib that looks something like this:
getUserClouds = function getUserClouds () {
return Clouds.find({ $elemMatch: { $eq: Meteor.userId() } })
}
This will work both on the client and on the server. But it will always return a Cursor pointing to 0 docs. So you'll need a publication:
Meteor.publish('userClouds', function () {
return Clouds.find({ $elemMatch: { $eq: this.userId } })
})

Meteor publication with calculations

I have 2 collections: Meteor.users and Projecs.
Users collection have field "projects" which contains array of user's project's ids.
"projects" : [
"jut6MHx6a7kSALPEP",
"XuJNvq7KTRheK6dSZ"
]
Also I have a publication for user's projects:
Meteor.publish('projects', function() {
var userProjects = Meteor.users.findOne(this.userId).projects;
return Projects.find({_id: {$in: userProjects}});
});
Everything works fine, but when I add new project (and update users ("projects" field) who are in this project) reactive publication doesn't works. Projects page doesn't contains recently added project. It works only when I refresh page.
Subscription made in router:
waitOn: function() {
return [
Meteor.subscribe('projects')
]
},
What should I do with this publication? Thanks a lot.
This is happening because Meteor.users is not reactive. I don't know what the reason behind but I saw many developers, specially developers who try to get famous by publish really cool articles about their awesome application, exposing the tokens.
So if some idiot publish the Meteor.users to the browser, it's a security flaw. It would be even worst if it was reactive because the token would be updated in realtime. Maybe this a block to newbie who don't really know that they're doing. Just my opinion about this decision.
This collection is design to be used for managing users and after the login, it makes no sense to use to store data, as it is designed.
Yea, this is a known "problem". Publish functions aren't reactive, so Meteor.users.findOne(this.userId).projects will only be evaluated when the client subscribes. You'll find a lot of information about this if you search for "meteor reactive joins", for example https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
In your case, the clients will always have access to their array of project ids, right? Then the simplest solution would probably be to do something like this on the client:
Tracker.autorun(function(){
var user = Meteor.user()
if(user){
Meteor.subscribe(user.projects)
}
})
So, when the client notices that the array of project ids has changed, it renews the subscription (I'm unsure if passing user.projects to the subscribe call is necessary, but I'm a bit afraid that the subscription isn't is renewed if it's called with the same arguments as before).
Using the reactive-publish package (I am one of authors) you can do:
Meteor.publish('projects', function () {
this.autorun(function (computation) {
var userProjects = Meteor.users.findOne(this.userId, {fields: {projects: 1}}).projects;
return Projects.find({_id: {$in: userProjects}});
});
});
Just be careful to limit the first query only to projects so that autorun is not rerun for changes in other fields.

Subscribe to add/remove of a collection which belongs to a parent document in meteor

Imagine I have 2 collections
Post {
_id: ...
title: ...
}
Comment {
_id: ...
postId: ...
text: ....
}
On a post detail page, I want to see the post title and all of its comments which must be reactive.
I can declare a Meteor.methods to return the post and its comments with one request but I dont know how to make the comments reactive.
I can get the post first and then Meteor.subscribe to its comments based on the post's id, but this solution requires 2 sequential requests which is not ideal.
How can I have both of them and still have comments reactive.
Thank you.
you can actually return more than one collection in a ´Meteor.publish´ function:
Meteor.publish("postWithComments", function(postId){
return [Posts.find({_id: postId}), Comments.find({postId: postId})];
});
if you subscribe to this your local mini mongo will receive both collections. The limit is that every cursor must be from a different collection. Read the docs
If the comments were embedded in the same database collection per post, then you could return the post and its associated comments in one go. If they are in separate collections, (as in your case) then you'll need to subscribe to / request both.
In terms of reactivity, Meteor's publish and subscribe functions automatically deliver fresh content from the server to the client. You can also pass Session variables into your client-side subscribe function, in which case you would use Deps.autorun to automatically rerun the request each time the Session variable changes. This is discussed in the Meteor publish and subscribe documentation.

How to know when user document loaded in Meteor Accounts

I understand that when writing code that depends on the collection being loaded into the client minimongo, that you should explicitly subscribe to the collection and pass in the appropriate callback for when it is finished loading.
My problem is that I store a lot of important subdocuments that my page needs to access in the users collection. I am using Meteor Accounts, and am trying to figure out a similar way to wait until the entire logged in user document is available. When using this to test:
console.log(Meteor.user());
the logged in case, it seems like it first registers an object with just the _id, and then sends the other fields later (I know I have to explicitly add other fields to publish from the server beyond email, etc.).
Is there a way for me to wait for the logged in user document to load completely before executing my code?
Thanks!
Deps.autorun (previously Meteor.autorun) reruns when something reactive changes, which might fit your use case:
Client js
Deps.autorun(function () {
if(Meteor.user() {
//Collection available
}
});
If you're using a subscription you can also use its callback. Have a read about it on the docs as you might have to customize it a bit, and remove the autopublish package as well as get your other collections set up to subscriptions
Server js:
Meteor.publish("userdata", function () {
//You might want to alter this depending on what you want to send down
return Meteor.users.find({}, {}});
});
Client js
Meteor.subscribe("userdata", function() {
//Collection available
});

Resources