meteoriron-routersubscription

Meteor Iron-Router: Wait for Subscription sequential


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!


Solution

  • 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.