Collection updates, but Template/Meteor subscriptions not rerun

Today I got myself in really odd situation when did it generally happens that data in the database collection is updating but subscriptions are not re running.

Following is the blaze template

<template name="budget">
  {{#if Template.subscriptionsReady}}
    {{#if haveBudget}}
      <div class="budget-wrapper">
        {{>budgetTable tableData=tableData}}
      </div>
    {{else}}
      <h2>Sorry either these reports don't have budget or <br /> you haven't generated it yet, after pdc short..</h2>
    {{/if}}
  {{else}}
    Loading data....
  {{/if}}
</template>

Below is my javascript code for this template

Template.budget.onCreated(function() {
  const tmpl = this;

  Session.set('currentYear',null);
  Session.set('pdcId',null);
  Session.set('decimalSign',null);
  Session.set('budgetData',null);
  Session.set('aId',null);

  tmpl.analysisData = new ReactiveVar(null);

  aId = FlowRouter.getParam('aId');
  cId = FlowRouter.getParam('cId');

  tmpl.autorun(function() {

    tmpl.subscribe('pdcFullBaseData', function() {
      completePDCObj = PDCFull.findOne({});

      pdcFullData = completePDCObj.pdcFullData;
      autoSearchData = createAutoSearchData(completePDCObj);
    });
    tmpl.subscribe('budgetCoreData',function(){
       budgetCoreData = BudgetCoreData.findOne({}).general;
    });
    Meteor.subscribe('pdcDocumentsWithAnalysisId',aId,function() {
      pdcDocument = DocumentsStorage.findOne({analysisId : aId, type: 'FULL_PDC'});
      pId = pdcDocument._id;
      decimalSign = pdcDocument.decimalSign;
      Session.set('pdcId',pId);
      Session.set('decimalSign',decimalSign);
    })

    Meteor.subscribe('analysisWithId',aId,function() {
      const analysisData = AnalysisStorage.findOne({_id : aId});
      if(analysisData.generateBudget) {
        tmpl.analysisData.set(analysisData);
      }else {
        tmpl.analysisData.set(null);
      }
      Session.set('aId',aId);
      Session.set('decimalSign',analysisData.decimalSign);
      Session.set('currentYear',analysisData.year);
      Session.set('budgetData',analysisData.budgetData);
      Session.set('budgetUploadedData',analysisData.budgetUploadedData);
      })
    })
})


Template.budget.helpers({
  tableData : function() {
    const analysisData = Template.instance().analysisData.get();

    if(analysisData.budgetData) {
      budgetTableData = analysisData.budgetData;
      Session.set('budgetData',budgetTableData);
      console.log(analysisData,budgetTableData)

      return {year : analysisData.year , tableBody : budgetTableData}
    }
    return null;
  },
  haveBudget: function() {
    const budgetData = Template.instance().analysisData.get().budgetData;
    return !!budgetData;
  }
})

Template.budgetTable.onCreated(function(){
  const tmpl = this;
  tmpl.budgetTableMode = new ReactiveVar('automatic');
  tmpl.autorun(function() {
    Meteor.subscribe('pdcDocumentsWithAnalysisId',aId,function() {
      pdcDocument = DocumentsStorage.findOne({analysisId : aId, type: 'FULL_PDC'});
      pId = pdcDocument._id;
      decimalSign = pdcDocument.decimalSign;
      Session.set('pdcId',pId);
      Session.set('decimalSign',decimalSign);
    })

    Meteor.subscribe('analysisWithId',aId,function() {
      const analysisData = AnalysisStorage.findOne({_id : aId});
      Session.set('decimalSign',analysisData.decimalSign);
      Session.set('currentYear',analysisData.year);
      Session.set('budgetData',analysisData.budgetData);
      Session.set('budgetUploadedData',analysisData.budgetUploadedData);
    })
  })
})
....

Below are my publications in the server/main.js file.

/* Publish analysis with id */
Meteor.publish('analysisWithId',function(id){
  console.log('analysis id',id);
  return AnalysisStorage.find({_id : id})
})

/*Publish pdcs with analysis*/
Meteor.publish('pdcDocumentsWithAnalysisId',function(analysisId) {
  console.log('analysisId ',analysisId);
  return DocumentsStorage.find({analysisId});
})

/* Publish PDCs */
Meteor.publish('pdc_documents', function pdcDocumentsPublication() {
  return DocumentsStorage.find({});
});

/* Publish Results */
Meteor.publish('documents_results', function documentsResultPublication() {
  return DocumentsResultStorage.find({});
});

/* Publish the pdcFullBaseData */
Meteor.publish('pdcFullBaseData',function() {
  return PDCFull.find({});
})
.....

I have events attached to the table which updates the database collection (AnalysisStorage). I can verify the updated result using robomongo which shows me updated results. Also if I hit refresh the table gets updated but since the subscriptions should re run the blaze template should automatically update itself.
I am stuck at this from yesterday, not able to wrap my head around it. Any help is greatly appreciated :pray:

P.S : Not super sure about it but I have put console in my publications. And can see those publication code never re run until I do page refresh. May they work this way, so not super sure.

That’s a lot of code to look at, but I have the following observations:

  1. tmpl.autorun(function() {...: this will only re-run if reactive dependencies within the autorun change. As far as I can see, there are only three places which may change reactively:
    • completePDCObj = PDCFull.findOne({}); and that is within a subscription with non-reactive parameters (actually, no parameters). That means the subscription will only ever be run once at initialisation.
    • Meteor.subscribe('pdcDocumentsWithAnalysisId',aId,function() {...: this will re-run if the route param changes.
    • Meteor.subscribe('analysisWithId',aId,function() {...: this will re-run if the route param changes.
  2. Publications are inherently not reactive, which you have discovered. The canonical way to re-run a publication is to re-subscribe (however, see point 1).
  3. A general point (not related to your issue):
    • Using Meteor.subscribe() means you will have to manage stopping the subscription yourself. Use template subscriptions (as you’ve already done with tmpl.subscribe()) to have Meteor manage those for you.

Check the Meteor Guide for more information about pub/sub.

3 Likes

Thanks for the really detailed insight and that makes complete sense to me now why not subscriptions are rerunning.
I actually was on the view that publications are reactive which obv. I was wrong.
Yes I was actually forget that to change , thanks though for mentioning about the Meteor level subscriptions.

PS: Though bit offtopic, I really like to know the approach how to manually retrigger the subscriptions as in mine case we have observed there is no reactive source, which is updating and gonna retrigger the subscriptions. so whats the good approach there ? @robfallows

Like to manually retrigger the subscriptions , I created a new Session variable and change it’s value when I successfully updates the database and use that session variable inside the tmpl.autorun
something like in this way,

tmpl.autorun(function() {
    if(Session.get("updatedTheTable")) {
      tmpl.subscribe('pdcFullBaseData', function() {
        completePDCObj = PDCFull.findOne({});

        pdcFullData = completePDCObj.pdcFullData;
        autoSearchData = createAutoSearchData(completePDCObj);
      });
      tmpl.subscribe('budgetCoreData',function(){
        budgetCoreData = BudgetCoreData.findOne({}).general;
      });
      tmpl.subscribe('pdcDocumentsWithAnalysisId',aId,function() {
        pdcDocument = DocumentsStorage.findOne({analysisId : aId, type: 'FULL_PDC'});
        pId = pdcDocument._id;
        decimalSign = pdcDocument.decimalSign;
        Session.set('pdcId',pId);
        Session.set('decimalSign',decimalSign);
      })
....

but yeah that didn’t work because the subscriptions doesn’t depend upon this value ?
so is it possible to manually retrigger those somehow ?

1 Like

What event exactly do you want to trigger a re-subscription? A change of aId value from the route (i.e. a change of route)? A ‘click’ event of some sort?

If you are subscribed to a document (e.g. using the pdcDocumentsWithAnalysisId subscription/publication) and make a change to that document via a Meteor method, the modifications to the document will be pushed to client without you having to do anything. Cursors in publications are live queries - e.g. DocumentsStorage.find({analysisId}) will ensure that that the client has the latest version of the document with the _id of analysisId at all times (because that’s what you’ve subscribed to). It is only if you want to change the analysisId value that you’d need to re-subscribe. In which case, you’d do something like this …

tmpl.autorun(function () {
  var aId = FlowRouter.getParam('aId');  // or var aId = Session.get('aId');
  if (aId) {
    Meteor.subscribe('pdcDocumentsWithAnalysisId', aId, function () {
      pdcDocument = DocumentsStorage.findOne({analysisId : aId, type: 'FULL_PDC'});
      pId = pdcDocument._id;
      decimalSign = pdcDocument.decimalSign;
      Session.set('pdcId', pId);
      Session.set('decimalSign', decimalSign);
    });
  }
}

I haven’t used Flow Router, but assume the getParam method is reactive (i.e. it will be the trigger for the re-subscription).

Also, there’s probably no real reason to set Session variables in the subscription callback. The live document is accessible on the client from minimongo. Just write var analysis = DocumentsStorage.findOne({_id: aId}); on the client (in a template helper) and you have a reactive data source (i.e. will trigger template re-draws if it changes) with all the published data from that document.

1 Like

A simple case of using reactivity from db to template:

SERVER (publications.js)

Meteor.publish('document', function (documentId) {
  return Documents.find({_id: documentId});
});

CLIENT (myDocument.html)

<template name="myDocument">
  {{#with document}}
    Name: {{name}}
    Content: {{content}}
  {{/with}}
</template>

CLIENT (myDocument.js)

Template.myDocument.onCreated(function () {
  var tmpl = this;
  tmpl.autorun(function () {
    tmpl.subscribe('document', Session.get('documentId'));
  });
});

Template.myDocument.helpers({
  document: function () {
    return Documents.findOne({_id: Session.get('documentId')});
  }
});

CLIENT (someOtherTemplate.js)

Template.someOtherTemplate.events({
  'click .document' : function () {
    Session.set('documentId', this._id);
  }
});

So, by clicking a document in someOtherTemplate, the Session variable’s documentId value is changed. Because Session.get is reactive and sitting in an autorun block, this triggers a re-run of the function in the autorun block, which includes a subscription to the document publication, using the new value for documentId. At this stage the publication code gets re-run with the new value and the new document is pushed to the client, while the subscription to the old document is automatically cleaned up. Because the Documents.find function on the client is reactive, as is the Session.get it uses, and these reactive data sources are in a template helper, the function in the template helper gets re-run and the elements in the template (that are based on the document being returned – i.e. {{name}} and {{content}}) are updated accordingly.

1 Like