Return all details just for one item, while returning basic details for everything else

Hi, I have thousands of Mongo documents and I need to show its list (or just what does belong to a logged on user), and also need to show full document details just for a selected item (all is on one page with two templates). Publishing all details makes the loading time about 4 times longer on localhost (2s vs 8s), so I want to limit data that are returned to the client.

While this reduces the loading time (not publishing “events”), I don’t know how to publish all details for a selected animal.

Meteor.publish('animals', function () {
  	if (Meteor.user()) {
		return Animals.find({ farm : Meteor.user().farm }, { fields: { name: 1, farm: 1 }  });
	} 
});

I can subscribe to this, but the other data is not there.

Meteor.publish('animalDetail', function(name) {
	if (Meteor.user()) {
		return Animals.findOne({farm: Meteor.user().farm, name: name})
	}
})
Meteor.subscribe('animals');
Meteor.subscribe('animalDetails', Session.get('whichAnimal'));

While the full list shows ok, the details won’t show anything (unless I also publish the attribute called “events”). I use the events array to render a nicely formatted list of events

Template.animalDetails.helpers({
    events() {
        if (Session.get('whichAnimal')) {
            let events = Animals.findOne(Session.get('whichAnimal')).events.map( (o) => { ...

I tried some experiments with

Template.animalDetails.onCreated(function() {
    this.autorun(() => {
        this.subscribe('animalDetails', Session.get('whichAnimal'));

But that doesn’t work (and I don’t really understand how that works).

Can you please tell me how to achieve that or if there is a good source of information for all those Tracker events, autorun, onCreated and onRendered and especially how to combine it together?

Thank you

Before I type up a longer response, why are you using Prasata.find on the server and Animals.findOne on the client?

Sorry, I was trying to translate my Czech names to English and missed that one out. It is corrected now.

Super basic, but here goes:

An autorun will re-run whenever any reactive data changes. In this case, when you set a new value to your Session variable, any autoruns that have a Session.get call inside will rerun.

You should use this so that your autorun and subscription is scoped to your template. That way, when your template is destroyed (when the user leaves), the subscription is automatically stopped.

Publications need to return a cursor, or an array, so findOne won’t work for you.

Template.animals.onCreated(function() {
  this.autorun(() => {
    const animalSub = this.subscribe('animals');
    // Then you can do things like animalSub.ready() 
    // to see if your data has arrived
  });

  this.autorun(() => {
    const animalDetailSub = this.subscribe('animalDetails', Session.get('whichAnimal'));
  });
});

// server.js
Meteor.publish('animals', function () {
  if (Meteor.user()) {
    return Animals.find({ 
      farm : Meteor.user().farm 
    }, {
      fields: { 
        name: 1, 
        farm: 1, 
       },  
    });
  }
});

Meteor.publish('animalDetail', function(name) {
  if (Meteor.user()) {
    return Animals.find({
      farm: Meteor.user().farm, 
      name: name,
    });
  }
});

Meteor is smart enough to be able to merge the data clientside, so that everything works. Your helpers would then look something like:

Template.animals.helpers({
  animals() {
    return Animals.find();
  },
  animalDetail() {
    return Animals.findOne({ name: Session.get('whichAnimal') });
  },
});

You should try not to use Session, but that’s probably a story for another day.

2 Likes

Thank you so much! So I was pretty close. After you reassured me that I was doing it the right way, I found another data-related problem (querying a wrong field, so I was dumb).

Your explanation of Tracker helped me heaps too!

I have kind of implemented your solution. I just have two different templates (animals and animalDetails) with their respective autoruns (I guess that is ok?). Now I have a weird glitch - the animals collection contains all the animals (Animals.find().fetch()) (one of the animals shows all details), but when I select an animal from the list, it appears in the details section, but disappears from the actual list! And when I select another one, the missing one comes back and the newly selected animal disappears.

Is the subscription providing data exclusively (the animal with all details can only be shown in one template)? I can’t think of another reason. And if so, then the only solution is to merge the two templates into one (probably doable, they are always shown together (so far))?

I know that Session variables should not be used unless you want a global variable (or so I think so). I will refactor the code to get rid of it eventually, but I am still in the prototyping stage.

Cool, glad to help!

Hm, that isn’t the way that the data should be working, so I’m wondering if it’s a helper issue?

You should be able to subscribe to a list of data with limited fields, and also to a specific animal with detail. Meteor handles merges for you so that overlapping data works pretty much the way you would expect it to.

I’m checking out for the night, but feel free to post some more template code and I’ll try to take a look.

1 Like

I am sorry to bother you with that. It was another data-related problem (the client code was filtering out animals with a specific status, and as the status was only available when the animal published all its details, only then it could disappear).