I use meteor vue to subcribe to a publication:
meteor: {
$subscribe: {
'gps': function() { return [ this.query ] }
}
},
The docs only offer the following option:
<div v-if="!$subReady.thread">Loading...</div>
However, I need to execute some code when the subscription is ready. Is there some kind of onSubscriptionReady hook?
If you use data from the subscription in your template, you can do something like:
beforeUpdate() {
if (this.$subReady['gps']) {
// do something
}
}
I do it this way in my app and so far it works fine. The problem is when you don't use data in the template because update hook isn't fired.
Related
whats the bestway to handle subscription based data. For example, you have a game where you have to create the character first before you can do any other things. Currently I thougth I can try to handle it with a onBeforeAction filter. So I have a global controller for every route that needs a the character.
DefaultController = LayoutController.extend({
onBeforeAction : function() {
var currentCharacter = Character.getCurrent.call({});
if(currentCharacter === undefined) {
this.render('CharacterCreate');
} else {
this.next();
}
},
waitOn() {
this.subscribe('characters.owned');
}
});
You have a route like this:
Router.route('/game', { controller: 'DefaultController' });
The problem is until the the collection is loaded the game template will shown. Is there a better approach like this? And another problem when a route needs a character it throws an exception until the subscription is loaded.
Just use a loading hook while the subscriptions are being loaded.
loading(){
this.render('myLoadingTemplate');
}
The loading hook is run automatically while waiting for subscriptions to be ready.
You might find my post on building a clean router.js file useful.
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'm aware of Template.onRendered, however I have the need to destroy and setup some plugins that act on the dom when the actual context is updated.
So saying I have content template, I'd need something similar to the following:
Template.content.onBeforeChange(function () {
$(".editor").editable("destroy");
});
Template.content.onAfterChange(function () {
$(".editor").editable();
});
Is there any current way I can achieve this with the existing Template api?
You should be able to detect a context change within a template autorun by looking at currentData like this:
Template.content.onRendered(function() {
this.autorun(function() {
if (Template.currentData()) {
// the context just changed - insert code here
}
});
});
I'm unclear if that works for your particular case because this technique only gets you the equivalent of onAfterChange.
I'm building an app using meteor and meteor router, and I would like to make a template helper for checking if the route is a specific one ({{#ifRouteIs login}}{{/ifRouteIs}}).
I had the same issue. Building on your answer, I found a working solution. It needs to go in the client side of Meteor.
Handlebars.registerHelper('ifRouteIs', function (routeName, options) {
if (Meteor.Router.page() === routeName) {
return options.fn(this);
}
return options.inverse(this);
});
According to meteor-router's README, you can get the current page with Meteor.Router.page(), so the helper might look like this:
Handlebars.registerHelper('ifRouteIs', function (routeName) {
return Meteor.Router.page() === routeName;
});