Reactive Mongo Find

What’s the appropriate methodology to make a mongo find query reactive without a poll? Is there a better way than polling every couple hundred milliseconds? I would love a code example to work off of.

You are gonna need to be more specific. Where are you expecting this be reactive that it is not?

My apologies. I should have been more explicit.

I’m using dburles:google-maps and I’d like to update the map on the fly. I know how to add/remove markers. So if I have documents going in/out I’m not entirely sure where to put the find and how to make it reactive. I would think it would below in onCreate in a Tracker.autorun?

If you are using Blaze - yes in the onCreated/onRendered handler you can start a Tracker.autorun (this.autorun) that will rerun every time any of its Reactive datasources are changed. (Session, Collection, ReactiveVar etc…)

// client code
this.autorun(function () {
        _.each(Collection.find({}).fetch(), function(colItem) {
               // reruns every time the client version of the Collection has updated.
               // ...
        })
});

Shouldn’t it be something like this? All I’m trying to do is monitor the collection for changes and then do some javascripty stuff.

// Home
Template.pageHome.onCreated(function(){

  var self = this;
  self.autorun(function(){
      self.subscribe('livestreams', function() {
          console.log('livestreams changed');
      });
  })

It depends on what “monitor the collection for changes and then do some javascripty stuff” means to you. If you have something like this:

// server.js
Meteor.publish('livestreams', function () {
  return LiveStreams.find();
});

then any docs added/removed/changed in that collection should be reflected in your subscription data.

It sounds like you are trying to listen for those changes on the client side? Then something like this:

// client.js
Template.myTemplate.onCreated(function () {
  const self = this;
  self.autorun(function() {
    const livestreamSub = self.subscribe('livestreams');
    if (livestreamSub.ready()) {
      const query = Livestreams.find();
      const handle = query.observeChanges({
        added (id, fields) {
          // your code to add the marker
        },
        removed (id) {
          // your code to remove the marker
        },
      });
    }
  });
});

This is exactly what I was looking for! Thanks.

It seems to be working, but breaks the rest of the app. Is there something in there that might halt execution of other things? Slowly pulling out code, once it hits: const livestreamSub = self.subscribe(‘livestreams’); nothing else seems to load. i.e. jQuery functions in onRendered simply don’t run. What would one have to do with the other?

And nothing in the console.

I’d simplify the code as follows:

Template.myTemplate.onCreated(function () {
  this.subscribe('livestreams', () => {
    const cursor = Livestreams.find();
    cursor.observeChanges({
      added(id) {
        // your code to add the marker
      },
      removed(id) {
        // your code to remove the marker
      },
    });
  });
});

Why? You have no reactive variables in the subscribe call, so there is no point putting it into an autorun and there are some open issues demonstrating unexpected behaviour when resubscribing to a publication (including with identical arguments). Perhaps better to avoid this unless absolutely necessary :wink:.

Also, observeChanges establishes a (reactive) livequery itself, so it’s not strictly necessary to put it into an autorun. Having said that, if you don’t stop the observer it will run forever and one way to get this done for you is to put the observer into an autorun.

observeChanges returns a live query handle, which is an object with a stop method. Call stop with no arguments to stop calling the callback functions and tear down the query. The query will run forever until you call this. If observeChanges is called from a Tracker.autorun computation, it is automatically stopped when the computation is rerun or stopped.

That gives you this:

Template.myTemplate.onCreated(function () {
  this.subscribe('livestreams', () => {
    const query = Livestreams.find();
    this.autorun(() => {
      query.observeChanges({
        added(id, fields) {
          // your code to add the marker
        },
        removed(id) {
          // your code to remove the marker
        },
      });
    });
  });
});

The only (minor) change I made was to use a callback function on the subscribe:

… If a function is passed instead of an object, it is interpreted as an onReady callback.

2 Likes

Ok, so this will mean that {{#if Template.subscriptionsReady}} won’t be fired, correct?

You still have a Template level subscription, which will become ready at some point, so Template.subscriptionsReady will continue to work.