[Solved] How to get data from the publication and use it in the template

Hello Guys. I am pretty new to Meteor.js. I need some help regarding getting data from the publication after I subscribe to it. This is my publication.

var _ = lodash;
Meteor.publish('getProjectOverView', function(jobId) {
    const candidatesArr = [];
    const matchedCandidates = Matchs.find({ jobListingId: jobId, candidateProfileCompleted: true }).fetch();
    for (const id in matchedCandidates) {
        const candidateObj = {};
        const candidateData = CandidateListings.findOne({ _id: matchedCandidates[id].candidateListingId });

        if (candidateData !== undefined) {
            const userData = Meteor.users.findOne({ _id: candidateData.userId });
            if (userData !== undefined) {
                const status = renderStatus(matchedCandidates[id].recruiterAction, matchedCandidates[id].candidateAction, matchedCandidates[id].selection);
                candidateObj.matchListingId = matchedCandidates[id]._id;
                candidateObj.userId = userData._id;
                candidateObj.candidateListingId = matchedCandidates[id].candidateListingId;
                candidateObj.email = userData.emails[0].address;
                candidateObj.fullName = userData.profile.fullName;
                candidateObj.status = status;
                candidateObj.jobId = jobId;
                candidateObj.importance = renderImportance(status);
            }
        }
        if (Object.keys(candidateObj).length !== 0) {
            candidatesArr.push(candidateObj);
        }
    }
    const orderedCandidatesArr = _.orderBy(candidatesArr, ['importance'], ['desc']);
    return orderedCandidatesArr;
});

And I subscribed to it in my template like this.

Template.projectoverviewtable.onCreated(function bodyOnCreated() {
    let instance = Template.instance();
    instance.autorun(() => {
        instance.subscribe('getProjectOverview', Router.current().params._id);
    });
});

The publication is returning an array containing data collected from different collections. I am not sure how can I use the array returned by the publication. Thanks for your help guys.

Hi @faisalblink, welcome to the forums!

First I can see that you’re returning an array of candidate objects.
Publication functions either need to return a cursor, an array of cursors, or use the low-level added/changed/removed events to communicate to the client.

Publications/subscriptions are special in that they reactively re-publish any changes that affect the cursor, and that they directly fill a matching collection on the client in minimongo.
These features are largely why they are limited to returning cursors.

If you want once-off access to the data, and the freedom to manipulate it like you’re doing in getProjectOverView, I would recommend doing it with a Meteor Method:

Meteor.methods({
  getProjectOverView(jobId) {
    // Same code as your publication function
  }
});

and on the client:

Template.projectoverviewtable.onCreated(function bodyOnCreated() {
    let instance = Template.instance();
    instance.projectOverview = new ReactiveVar();
    instance.loading = new ReactiveVar(true);
    instance.autorun(() => {
        Meteor.call('getProjectOverview', Router.current().params._id, (err, result) => {
          instance.loading.set(false);
          instance.projectOverview.set(result);
        });
    });
});

If you want to keep the reactivity, you can use a package like reywood:publish-composite, to make the joins.
Or use a more relational querying tool like Grapher, or go whole hog into Apollo / GraphQL

Or, you can use the low-level interface using this.added, this.changed and this.removed, in tandem with Collection.find().observeChanges to tell your publication, exactly what to do when the database changes and exactly what to update on the client

The other alternative would be to simplify the publication:

Meteor.publish('getProjectOverView', function(jobId) {
  const matchedCandidates = Matchs.find({
    jobListingId: jobId,
    candidateProfileCompleted: true,
  }).fetch()
    .map(doc => doc._id);

  CandidateListings.find(
    { _id: { $in: matchedCandidates } },
    { sort: { importance: -1 } } // -1 === DESC
  );
});

And de-normalize some of the extra data (like userId) into the document.
The only trouble left then is the computed status and importance. You could either update these in response to other events, or have a regular job that occasionally updates these values on a timer.

Really, the Meteor.method approach requires the least re-factoring and is the most straightforward, so that’s my personal recommendation

@coagmano … Thanks for the replies … To me the Meteor.method approach looks more doable so let me try and implement that.
Also one more thing I need to ask if you can provide your suggestion on it. As I mentioned in my question that the data is being collected from different collections of the database. So at first I was subscribing to the publications in my template’s onCreated method like this.

Template.projectoverviewtable.onCreated(function bodyOnCreated() {
    let instance = Template.instance();
    // instance.subscribe('getRecruiterJobs');
    instance.subscribe('getJobCandidates');
    instance.subscribe('getCandidateData');
    instance.subscribe('getCandidateEmail');
});

But doing this took a lot of time in loading the data … Any suggestion on how can I make this process fast ?

Should I load data preemptively ? If yes how should I do it.

Please do let me know if my question does not make sense to you I will try to explain it to you in a better way. Thanks

Ideally you want to subscribe to the smallest subset of data needed for that component. So usually you’ll pass some parameter to the server to filter with. There are also some packages that help by caching the subscriptions, which is really useful when you might need the same data between non-contiguous views.

It’s a lot harder when you’ve got to join on multiple collections, which is when packages like Grapher or GraphQL engines end up really valuable.

Methods are almost always the fastest way to load data, at the cost of reactivity and ease of access (no minimongo)

Unless you’re talking really large datasets, pre-loading isn’t normally needed.
I often use preloading (in a startup script), when I’m working on kiosk apps, because I know it will run a long time, I don’t want load times, and I don’t care about consuming large amounts of RAM :smiley:

1 Like

Thanks … ok then let me try th method approach see what are the results.

Thank you … the Method approach worked really well

1 Like