I'm using Iron Router. I have a RouterController that looks something like this:
var loggedInUserController = RouteController.extend({
layoutTemplate: "GenericLayout",
waitOn: function () {
return Meteor.subscribe("TheDataINeed");
}
});
And I have a route defined which uses this controller to wait for the 'TheDataINeed':
Router.route("/myapp", {
name: "Landing",
controller: loggedInUserController,
data: function () {
if(this.ready()){
return {content: "page-landing"};
}
}
});
Now, the problem is the data I am subscribed to is conditional: meaning, depending on the user's role, I publish different data, like so:
if (!Roles.userIsInRole(this.userId, 'subscribed') ) {
return [
myData.getElements({}, { fields: { _id: 1, title: 1}, limit: 5 })
];
} else {
return [
myData.getElements({}, { fields: { _id: 1, title: 1} })
];
}
When the user's role is not 'subscribed', I limit the published data to 5 elements.
The problem is publishing is not reactive, so when the user changes his role for the first time to 'subscribed' and I navigate to my route ("/myapp"), the user still sees the limited number of elements instead of all of them.
Is there a way to manually re-trigger the subscription when I am loading this route? If possible, I'd like to do this without adding new packages to my app.
Not sure about that approach but can you try to set session value in route instead of subscription code. Then in a file on client side where your subscriptions are you can wrap Meteor.subscribe("TheDataINeed") in Tracker.autorun and have a session as a subscription parameter. Every time that session value is changed autorun will rerun subscription and it will return you data based on a new value.
Related
In Meteor, one can add additional fields to the root-level of the new user document like so:
// See: https://guide.meteor.com/accounts.html#adding-fields-on-registration
Accounts.onCreateUser((options, user) =>
// Add custom field to user document...
user.customField = "custom data";
return user;
});
On the client, one can retrieve some data about the current user like so:
// { _id: "...", emails: [...] }
Meteor.user()
By default, the customField does not exist on the returned user. How can one retrieve that additional field via the Meteor.user() call such that we get { _id: "...", emails: [...], customField: "..." }? At present, the documentation on publishing custom data appears to suggest publishing an additional collection. This is undesired for reasons of overhead in code and traffic. Can one override the default fields for Meteor.user() calls to provide additional fields?
You have a couple of solutions that you can use to solve this.
Null Publication
Meteor.publish(null, function () {
if (this.userId !== null) {
return Meteor.users.find({ _id: this.userId }, { fields: { customField: 1 } });
} else {
return this.ready();
}
}, { is_auto: true });
This will give you the desired result but will also result in an additional database lookup.. While this is don't by _id and is extremely efficient, I still find this to be an unnecessary overhead.
2.Updating the fields the Meteor publishes for the user by default.
Accounts._defaultPublishFields.projection = { customField: 1, ...Accounts._defaultPublishFields.projection };
This has to be ran outside of any Meteor.startup blocks. If ran within one, this will not work. This method will not result in extra calls to your database and is my preferred method of accomplishing this.
You are actually misunderstanding the documentation. It is not suggesting to populate and publish a separate collection, just a separate publication. That's different. You can have multiple publications/subscriptions that all feed the same collection. So all you need to do is:
Server:
Meteor.publish('my-custom-user-data', function() {
return Meteor.users.find(this.userId, {fields: {customField: 1}});
});
Client:
Meteor.subscribe('my-custom-user-data');
I am doing a find on a collection of nodes and am then storing the info in a variable.
I am then console.logging this data to see the contents of it. The thing is, my data stays an empty array for a split second and then a new console.log is done with an array of data. See my code below:
Template.temperature.helpers({
node() {
let node = Nodes.find({_id:Session.get('selectedNode')});
console.log(node);
return '';
}
});
My console output:
1: []
2: [Object]
What could be the reason for this?
In your router, subscribe within waitOn:
Router.route('/home', {
waitOn: function() {
return [
Meteor.subscribe('nodes'),
];
},
action: function() {
if (this.ready()) {
this.render();
}
},
});
This will ensure that the route will wait until the subscription is completed before executing the route. It uses the meteor loading hook, so the wait will utilize whatever loading screen or animation you ahve setup.
I would to show a list of all users, in my template.
I have:
//publications.js
Meteor.publish('users', function() {
return Meteor.users.find({}, { fields: {username: 1, profile: 1} });
});
//router.js
Router.route('/users/add/:_id?', {name: 'users.add', controller: 'UserAddController'});
UserAddController = RouteController.extend({
subscriptions: function(){
return [ Meteor.subscribe('hospitals'),
Meteor.subscribe('roles'),
Meteor.subscribe('users') ];
},
action: function() {
this.render('addUser', {
data: function(){
return { hospital_id : this.params._id }
}
});
}
});
//client
Template.listUsers.helpers({
users: function() {
return Meteor.users.find({});
}
});
But the list keep showing only the current logged-in user. I have created a list of users using Account.createUser() function What am I doing wrong?
Thanks.
You have to subscribe to a publication using this.subscribe() in subscriptions hook:
// a place to put your subscriptions
subscriptions: function() {
this.subscribe('items');
// add the subscription to the waitlist
this.subscribe('item', this.params._id).wait();
}
Or use waitOn:
// Subscriptions or other things we want to "wait" on. This also
// automatically uses the loading hook. That's the only difference between
// this option and the subscriptions option above.
waitOn: function () {
return Meteor.subscribe('post', this.params._id);
}
By default, Meteor publishes the current user. I see that you have a addUser template and a listUsers template. The problem is that while addUser is subscribed to the users publication, listUsers is not (this would depend on what else you have in your router of course). To fix this, change the call to this.render to render the listUsers template. Then, your users helper should work, and you can render the information however you like.
I tested this with my own app (the Microscope project from DiscoverMeteor) and it worked for me. Hope it works for you too. Comment here if not, and be sure to accept this answer if it worked. =)
I have an app where each User has a Counter object associated with it. The User is subscribed to the corresponding Counter.
Each time a user submits the form, I want to save the current value of the User's Counter to the form. However, the client and server disagree on what Counter object to use.
Counters = new Mongo.Collection("counters");
Router.route('/register', { name: 'insertRegistration',
waitOn: function() {
return [ Meteor.subscribe('counters') ];
},
});
RegistrationSchema = new SimpleSchema({
somevalue: {
type: String,
autoValue: function() {
console.log(Counters.findOne()); // Different results on client and server
return "whatever";
}
}
});
if (Meteor.isServer) {
Meteor.publish('counters', function() {
return Counters.find({ user_id: this.userId });
});
}
Counters.findOne() on the client picks the Counter object associated with the current user.
Counters.findOne() on the server picks a Counter object out of the collection of all Counter objects.
I think this is because the Client asks for Counters and gets what it's subscribed to, while Server asks for Counters and gets the Collection.
Is there a way to consider the publish/subscribe rules on the server?
On the server, do
Counter.findOne({ user_id: this.userId })
to get the correct counter for the user.
I'm defining a route that will show an appointment for a patient. I would like the template to show both the patient information and the appointment information.
I have this published:
Meteor.publish('userAppointment', function(appointmentId){
check(appointmentId, String);
var userId = Appointments.findOne(appointmentId).patientId;
return [
Appointments.find({_id: appointmentId}),
Meteor.users.find({_id: userId}, {fields: {profile: true, emails: true}})
];
});
Unfortunately Iron Router doesn't seem to be successfully waiting on the data subscription to complete before it tries to set the data context.
Note where I put debugger:
Router.route('/admin/appointment/:id', {
name: 'AppointmentShow',
waitOn: function(){
return [
Meteor.subscribe("userAppointment", this.params.id)
]
},
data: function(){
var appointmentId = this.params.id;
debugger
var patientId = Appointments.findOne(appointmentId).patientId;
return {
appointment: Appointments.findOne(appointmentId),
patient: Meteor.users.findOne(patientId)
}
}
});
At the time when debugger stops the code, when I do Meteor.users.find().fetch() and Appointments.find().fetch() in the console only the currently logged-in user (me) is available and there are no appointments available.
I expect to see two users (me and the patient) and one appointment available because that's the data that should be available after the waitOn has finished subscribing.
Am I missing something here?
EDIT----- Still doesn't make sense to me ------
When I change my route to this:
Router.route('/admin/appointment/:id', {
name: 'AppointmentShow',
waitOn: function(){
return [
Meteor.subscribe("userAppointment", this.params.id)
]
},
data: function(){
var appointmentId = this.params.id;
return {
appointment: Appointments.findOne(appointmentId),
// patient: Meteor.users.findOne(Appointments.findOne(appointmentId).patientId)
}
}
});
Appointments.findOne(appointmentId) returns an object:
{
_id: "23efref34qr2",
reason: "coughing",
patientId: "785g45g4f"
}
When my data function only returns
appointment: Appointments.findOne(appointmentId)
it works. But if I have it also return
patient: Meteor.users.findOne(Appointments.findOne(appointmentId).patientId)
I get an error message (can't read property 'patientId' of undefined.) Huh? It was just defined on the line above!
To clarify, I think you should be allowing your data function to run (and rerun when collections are populated), but be careful to make sure your function doesn't throw an error when it runs before data is available. This is a general Meteor pattern.
data: function(){
var appointmentId = this.params.id,
appointment = Appointments.findOne(appointmentId);
return { appointment: appointment,
patient: Meteor.users.findOne(appointment ? appointment.patientId : null) }
}
Sorry about the formatting, I'm doing this from an awful phone...
The problem seems to be that it runs the data function() before running the waitOn, and is therefore timing dependent. I have seen the same problem, and also had to check if the data was actually there.