[SOLVED] Filtering reactive records in a collection based on another collection values


#1

I need your help to find a good pattern to follow when I want to filter collection X records based on a second collection Z values.

Reactivity in Meteor collection is extremely useful to automate screen updates when the corresponding collection changes. This is good and simple when all users need to visualize that change. Sometimes you want to keep those changes private to each user and in this case you need to store them in a separate data structure ─ like a user profile collection.

My question is how to do that without losing reactivity benefits that Meteor provides on collection X.
This is a sample case to better understand my issue:

Let’s suppose I have a list of public channels identified by _id and name. I want to use 1 collection and show those channels in 2 separate lists on the screen ─ according some user’s preference. Let’s say the user can pin/star a channel and in that case I need to show the channel on a separate list on the screen (pinned channels list). I want to store the list of channels the user pinned on ‘profiles’ collection:

_id : <id>, userId : <identify the user> pinnedChannels : [ <channelId>, ... ,<channelId>]
On the screen I want to show 2 lists:
List A : pinned channels
List B : all the others

Channels collection is shared among users.
In my understanding the code to show List B, for example, could be something like this:

Template.channels.helpers({ channels : function() { return Channels.find().map(function(c) { userProfile = Profile.findOne({userId : Meteor.userId()}); if (userProfile.pinnedChannels.indexOf(c._id) === -1) return c; else console.log('do nothing'); }); } });
I’m basically trying to use map() function to filter records and keeping the whole thing reactive so that changes in the user profile (pin or unpin channels) can be automatically reflected on the screen on channels lists:

<template name="channels"> {{#each channels}} {{> channel}} {{/each} </template>

I need to deal with many cases similar to this one and I would appreciate your opinion.


#2

So what’s not working here for you specifically? What exactly would you like to know or discuss or find a better way of solving for? (Maybe I just missed it?)

One thing I notice is that you’re misusing the map function in order to filter the find result, so you’re getting undefined values in between actual ones. What would be more appropriate and clearer is to use Collection.find().fetch().filter( ... ) (or use lodash _.filter if this code is supposed to also run on older browsers and you’re also not using a transpiler here (like Babel)).

(And the other thing would be of course pulling the userProfile var outside of the function call that’s done on each item in the collection, because that never changes. But that’s just performance optimization and potentially irrelevant because that’s probably just example code with a little sloppiness mixed in :wink: )


#3

Is your code not working? or it work the first time and will not react if any changes to the collection after rendering?

If yes, then you can use Tracker on your subscription or Observe in your collection


#4

Hey @seeekr and @ralpheiligan thanks.

I didn’t know the existence of the filter function. I reassembled my thought in a sample here https://github.com/massimosgrelli/meteor-filter-sample and… it worked. So, thanks.

I believed that using collection.find().fetch() sequence of calls the resulted in a non reactive behavior.
Apparently I was wrong ─ see my sample.

BTW I cannot find documentation about filter(). Do you have any url to suggest?


#5

It’s my first time to see a filter function. Anyway thanks for sharing your code and I have to look at it and learn from It =)


#6

#7

It’s the standard Array.filter method! See here for exaple: http://devdocs.io/javascript/global_objects/array/filter
Or google for it and find the Mozilla docs (MDN). Array has .map, .filter, .forEach, and you can use all of them in server code, on reasonably modern browsers (IE < 9 is the only culprit here I believe) or using polyfills/transpilers. Or just going with underscore’s / lodash’s equivalent which is of course supported everywhere. (See here: http://devdocs.io/lodash/index#filter)


#8

Sure, it’s an array function. Thanks


#9

Exactly. If you do .fetch() on the server in Meteor method, then you lose reactivity (instead of using publish and returning the cursor without fetch()). But on the client you keep reactivity because Meteor is smart enough to see that you’re using a collection and so it will just depend on the collection, regardless of what exactly you do with it!


#10

Thanks. I learned something new :smile: