How to respond to this.added() on the client?


#1

The title of the post says it all, I think. According to the docs, when you call this.added(), you are notifying the client that a document has been added to the collection:

Call inside the publish function. Informs the subscriber that a document has been added to the record set.

But how do you respond to that message on the client? The only callbacks that I’ve seen for subscribe() are onReady and onStop. Is there a way to register a callback inside of subscribe that will handle that this.added() call? Or is there another method for handling this.added() on the client?


#2

The only way to be notified on the client is if you add an observer to the collection, for example like this (typed this from memory, so might need some tweaking)

Template.observeTest.onCreated(function(){
 var instance = this;
 instance.observer = null;
 instance.subscribe('collection', function(){
  // subscription is ready
  instance.observer = Collection.find().observeChanges({
   added: function(id, fields) {
    // react to the new record
   }
  })
 });
});
Template.observeTest.onDestroyed(function(){
 var this = instance;
 if(instance.observer) {
  instance.observer.stop();
 }
})

#3

Thanks so much. I was trying something similar earlier (on the client) and I was frustrated b/c in the callback to observeChanges(), “this” was evaluating to the cursor and not the React component that I was calling it from. Which made it tough to respond appropriately! But by following your example, now “this” is evaluating as expected.

What baffles me is what this.added() does, when you use it on the server in a publish function (the only place you can use it, I think.) Because the above code works without it. I thought maybe it was to limit the fields that were sent to the client in the callback (the 3rd param to this.added() is ‘fields’). But that parameter has no effect on the “fields” argument in added: and like I said, it works without even using this.added().

So what does this.added() do? I’m just curious now, b/c your code example solved my problem, but it would be nice to know what it does.


#4

On the server you need to call this.added to actually add the document to the collection published to the client. I used to use this in my publications to pull in data from other collections, but have moved away from that pattern since observeChanges has a documented memory leak, or so I read.

Instead I publish an array of cursors for some publications now. For example, lets say my collection Comments contains a field userId, which actually references the Meteor.users collection, you can do the following

Meteor.publish('comments', function(date){
 let comments = Comments.find({createdAt: date});
 let userIds = [];
 comments.forEach(function( c ) {
  if(userIds.indexOf(c.userId)<0) userIds.push(c.userId)
 });
 return [ comments, Meteor.users.find({_id: {$in: userIds}},{fields: {'profile.name':1}}) ];
});

#5

Mostly, you never need to use this.added() (or this.removed() or this.changed()). Meteor takes care of that for you when you publish simple collection.find()s.

The reason those low level calls exist is so you can write publications which don’t use MongoDB at all, or you can code complex operations which need to result in what looks like a single collection on the client. One typical non-MongoDB use is to consume a REST API and publish the results to the client, as if they were coming from a MongoDB collection.

For a simple, but contrived use case, consider this example which publishes a random number every second:


#6

That’s very cool! Thanks so much for the explanation and the sample. I had no idea that you could populate a collection that way. I put a feature into a game that I made with Meteor that displays a random weather report from somewhere on the globe once a minute. But, not knowing any better, I had each client ask the server to do an API call for the weather report. But with this method, I can do just one API call per minute and publish it to all the clients that need it. Less network traffic and it guarantees I won’t exceed the free tier limit on the weather API I use!


#7

Or for example using this.added to create a publication to publish files located in some folder on the server.

import { Meteor } from 'meteor/meteor'
import fs from 'fs'
import { _ } from 'meteor/underscore'

Meteor.publish('backgrounds', function () {
    const self = this;
    const meteorRoot = fs.realpathSync(`${process.cwd()}/../`);
    const publicPath = `${meteorRoot}/web.browser/app/`;
    const backgroundPath = `${publicPath}/backgrounds/`;
    const bgs = fs.readdirSync(backgroundPath);
    _.each(bgs, function (background) {
        const backgroundName = background.split('.')[0].toProperCase()
        self.added('backgrounds', background, { name: backgroundName, value: `/backgrounds/${background}` });
    })
    this.ready();
});