I am trying to switch my app from autopublish to publish/subsribe. After trying to set publish/subscribe up or my first collection, none of my items from the collection are being displayed anymore, and I am at a loss at what might be going wrong.
Can someone spot a mistake in my code below or give any hints as to how to debug this type of problem?
client/routes.js
Router.route('/slug', {
name: 'route.name',
title: 'My Page',
template: 'TemplateName',
subscriptions: function() {
this.subscribe('myPublication');
},
action: function() {
this.render();
}
});
server/lib/collectionLib.js
...
Meteor.publish('myPublication', function() {
return MyCollection.find();
});
client/myCollection/view/mycollection-list.html
...
{{#each myCollection}}
...
{{/each}}
...
I was missing the exposure of the collection to the template. See answer provided here: https://forums.meteor.com/t/publish-subscribe-not-working-how-to-debug/13127/6?u=codejak
Oh I see now, unless you are doing MyCollection.find() in your template helper you need to do it in the route:
data: function () {
return MyCollection.find({}).fetch();
}
Related
My code was working fine until I implemented publish/subscribe. I followed the basic tutorial and checked the source code and I'm not doing anything different. Everything builds and runs but nothing from MongoDB gets displayed in the Blaze template.
imports/api/features.js
if (Meteor.isServer) {
Meteor.publish('features', function featuresPublication() {
return Features.find({});
});
Meteor.publish('comments', function commentsPublication() {
return Features.find({}, {fields: {comments: 0}});
})
};
client/main.js
Template.body.onCreated(function bodyOnCreated() {
Meteor.subscribe('features');
});
client/main.html
<body>
<h1 id="title">Feature Requests</h1>
{{#if currentUser}}
<button class="ui green button create" id="create">Add a New Feature Request</button>
{{> requestForm}}
{{#each features}}
{{> feature}}
{{/each}}
{{else}}
{{> loginButtons}}
{{/if}}
</body>
Edit #1
Before I ran meteor remove autopublish my code looked like this and worked:
Template.body.helpers({
features() {
return Features.find({}, {sort: {createdAt: -1}});
},
comments() {
return Features.find({}, {fields: {comments: 0}});
},
});
Edit #2
Thanks to everyone who contributed an answer. I fundamentally misunderstood how publish/subscribe worked. I didn't realize I still needed to call return Features.find({}) after I subscribed. Here's my working code:
import { Features } from '../imports/api/features.js';
import '../imports/api/features.js'
Template.body.onCreated(function bodyOnCreated() {
Meteor.subscribe('features');
});
Template.body.helpers({
features: function() {
return Features.find({});
}
});
Disregard the first answer. The lack of an autorun is what first caught my attention but since you're not passing any args to subscribe it is not needed.
My next question would be: In client/main.html, where is the reference to features coming from? Is there a features helper on Template.body? If not, you'll need to add it:
Template.body.helpers({
features: function() {
return Features.find({});
}
});
Also, see Meteor Subscribe & Publish with external api
Try this:
Template.body.onCreated(function() {
const self = this;
self.autorun(() => {
self.subscribe('features');
});
});
Also, see https://guide.meteor.com/data-loading.html#organizing-subscriptions.
I see you are using the imports directory. Have you remembered to import your publication file to the server/main.js file?
server/main:
import 'imports/path/to/publications.js'
I have the following route defined in my iron-router:
this.route("/example/:id", {
name: "example",
template: "example",
action: function () {
this.wait(Meteor.subscribe('sub1', this.params.id));
this.wait(Meteor.subscribe('sub2', <<data of sub1 needed here>>));
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
I want to wait for sub1 and sub2 before rendering my actual template. The problem is that I need a piece of data which is part of the result of sub1 for the sub2 subscription.
How can I wait sequential for subscriptions? So that I can split the wait in two steps and wait for my first subscription to be finished. Then start the second subscription and then set this.ready() to render the template?
A workaround that I thought of was to use Reactive-Vars for the subscriptions and dont use .wait and .ready which is provided by iron-router. But I would like to use a more convenient solution provided by iron-router or Meteor itself. Do you know a better solution for this?
Thanks for your answers!
Publish Composite Package:
If the second subscription is reactively dependent on certain fields from the first dataset -- and if there will be a many-to-many "join" association, it might be worth looking into reywood:publish-composite package:
It provides a clean and easy way to manage associated subscriptions for collections with hierarchical relations.
Publication:
Meteor.publishComposite('compositeSub', function(id) {
return {
find: function() {
// return all documents from FirstCollection filtered by 'this.params.id' passed to subscription
return FirstCollection.find({ _id: id });
},
children: [
find: function(item) {
// return data from second collection filtered by using reference of each item's _id from results of first subscription
// you can also use any other field from 'item' as reference here, as per your database relations
return SecondCollection.find({ itemId: item._id });
}
]
}
});
Subscription:
Then you can just subscribe in the router using:
Meteor.subscribe('compositeSub', this.params.id);
Router hooks:
As a suggestion, hooks in iron-router are really useful, as they take care of a lot of things for you. So why not use the waitOn hook that manages this.wait and loading states neatly?
this.route('/example/:id', {
name: "example",
template: "example",
// this template will be rendered until the subscriptions are ready
loadingTemplate: 'loading',
waitOn: function () {
// return one handle, a function, or an array
return Meteor.subscribe('compositeSub', this.params.id);
// FYI, this can also return an array of subscriptions
},
action: function () {
this.render();
}
});
You can use the configure option to add a template for loading event:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading'
});
Note regarding the comment in question:
If both subscriptions only depend on the same id parameter passed to it, you can use the following, as mentioned by #abj27 in the comment above -- however, this does not seem to be the case, going by your example:
Publication:
Meteor.publish("subOneAndTwo", function (exampleId) {
check(exampleId, String);
return [
FirstCollection.find({ _id: exampleId });
SecondCollection.find({ firstId: exampleId })
];
});
Subscription:
Meteor.subscribe('subOneAndTwo', this.params.id);
So just check what you need and use a solution accordingly.
https://github.com/kadirahq/subs-manager
With this package, you can assign a subscription to a variable. Then, you can check that variable's ready state. I just got this working... after years of trying to understand.
Here is my code snippet that works, but oddly I had to wrap it in a 1ms timeout to work...
```
Router.route('/activity/:activityId', function (params) {
var params = this.params;
setTimeout(function(){
var thePage = window.location.href.split("/");;
window.thePage = thePage[4];
dbSubscriptionActivites.clear();
window.thisDbSubscriptionActivites = "";
window.thisDbSubscriptionActivites = dbSubscriptionActivites.subscribe("activityByPage", window.thePage);
Tracker.autorun(function() {
if(window.thisDbSubscriptionActivites.ready()) {
dbSubscriptionComments.clear();
window.thisDbSubscriptionComments = "";
window.thisDbSubscriptionComments = dbSubscriptionComments.subscribe('fetchComments', "activity", Activities.findOne({})._id);
BlazeLayout.render("activity");
$('body').removeClass("shards-app-promo-page--1");
}
});
},1); // Must wait for DOM?
});
```
Examine: window.thisDbSubscriptionActivites = dbSubscriptionActivites.subscribe("activityByPage", window.thePage);
I'm setting as a window variable, but you could do a const mySub = ...
Then, you check that in the autorun function later.
You can see there is where I am doing subscriptions.
I suppose I really should move the BlazeLayout render in to another .ready() check for the comments.
i am struggling with adding in pagination for my forums. Could you help me out? Basically, I was expecting on my forums page, to only see 10 posts. but it is returning all of them. The "Load More" button also does nothing (it seems like).
I am using this package: Paginated Subscription
Here is the code I am using:
if (Meteor.isClient) {
Deps.autorun(function() {
var handle = Meteor.subscribeWithPagination('posts',10);
});
Template.postsList.helpers({
'posts': function(){
return Posts.find({});
}
});
Template.postsList.events({
'click .btn': function(){
handle.loadNextPage();
}
})
}
if (Meteor.isServer) {
Meteor.startup(function () {
Meteor.publish("posts", function(limit){
return Posts.find({}, {limit: limit});
});
});
}
I don't think the var handle = Meteor.subscribeWithPagination('posts',10); Needs to be in the Deps block unless you are passing some reactive parameter to thehandle.
See this Issue on Github.
You can use Kurounin subscribe based pagination.It works for me.In atmospherejs, there is an another react pagination you can check it in Kurounin repository
I have already answered this kind of pagination to one of question. Please refer the exact code that can help you.
Meteor Pagination Issue
In your publication you must use sort (as documentation says)
// Using sort here is necessary to continue to use the Oplog Observe
Driver!
// https://github.com/meteor/meteor/wiki/Oplog-Observe-Driver
Meteor.publish("posts", function(limit){
return Posts.find({}, {
limit: limit,
sort: {
createdAt: -1
}
});
});
I am trying to create a route for a user profile page, but when I visit the route it shows up as a completely blank page and with no errors in the terminal. Nothing whatsoever is shown, including static HTML. Here's the code:
routes.js
Router.route('/user/:_id', function () {
this.render('user');
}, {
name: 'user',
data: function(){
return Users.findOne({_id: this.params._id})
}
});
user.html
<template name="user">
<p>hello</p>
</template>
At the moment, I am using the default user accounts package and have not added any publication or subscription code.
Are you sure Users is an existing collection?
At the moment, I am using the default user accounts package and have
not added any publication or subscription code.
In that case, with autopublish enabled, your problem is probably solved by changing
data: function(){
return Users.findOne({_id: this.params._id})
}
into:
data: function(){
return Meteor.users.findOne({_id: this.params._id})
}
although it's strange this doesn't throw an error in your console...
Not sure if this is the reason, but I think that with multiple options for the route, you should incapsulate this.render in an action parameter. Something like this:
Router.route('/user/:_id', {
name: 'user',
data: function() {
return Users.findOne({_id: this.params._id})
},
action: function () {
this.render('user');
}
});
Source
I get an error in the terminal: "Users is not defined":
http://meteorpad.com/pad/eciFidhwHmLhjWmF3/Leaderboard
In your data function, try substituting Meteor.users.findOne({_id: this.params._id})
If you fix the HackPad I listed, Meteor.users won't work since the current version of HackPad doesn't support a late enough version of Meteor with Meteor.users. However, if you comment out your data function, you should at least see the page.
In Iron-router, we can pass the data to a page in the data field. For example:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate',
data: function () {
return {
title: getTitle(),
description: getDescription(),
}
}
});
});
In the template, title and description are actually some value passed to subtemplates:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
Since the data field in the iron-router is reactive, whenever a session variable change, the data field is recalculated.
In this case, however, the session variable in getTitle function only changes the template "titleTemplate", and the session variable in getDescription() function only changes the template "descriptionTemplate".
If the session variable in the getTitle() function changes, I would like to only execute the getTitle() function, and do not execute the getDescription() function. If possible, I would also like to only render the "titleTemplate" and do not render "descriptionTemplate".
I wonder whether that is possible. If this is not the right way of writing the Meteor application, what is a better way to do it?
Thanks.
This is an interesting situation. Despite the fact that the getTitle and getDescription functions may be dependent on completely different reactive variables, they will both be recomputed whenever either one of them changes.
One possible solution is to pass the functions themselves instead of the result of calling the functions. That may or may not be convenient depending on how they are used in the sub-templates, but it will prevent them from both being run every time. Here is a simple example:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
<template name="titleTemplate">
<p>{{excitedTitle}}</p>
</template>
<template name="descriptionTemplate">
<p>{{this}}</p>
</template>
var getTitle = function() {
console.log('computed title');
return Session.get('title');
};
var getDescription = function() {
console.log('computed description');
return Session.get('description');
};
Router.map(function() {
this.route('home', {
path: '/',
template: 'myTemplate',
data: function() {
return {
title: getTitle,
description: getDescription
};
}
});
});
Meteor.startup(function() {
Session.setDefault('title', 'title1');
Session.setDefault('description', 'description1');
});
Template.titleTemplate.excitedTitle = function() {
return "" + (this.toUpperCase()) + "!";
};
From the console you can change the session variables (title and description) and you will see that only one function will be run at a time. I hope that helps.
One way to solve this is to not use the data context, but just use template specific helpers. Since I don't know what your getTitle and getDescription function do, I can't tell whether that is an option for you. It depends on whether you need to use the this object in those functions and need this to refer to the route object or not. If not, then the following seems like the better solution:
JS:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate'
});
});
Template.myTemplate.title = getTitle;
Template.myTemplate.description = getDescription;
HTML:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>