Imagine you have multiple collections you want displayed on a single social news feed, for example Posts
and Users
(new signups). How would you reactively display both in a list sorted by creation date?
You publish the entities separately, and in the client, sort each of them by date, and merge them into a single sorted array, which you then display in an {{#each}}
iterator. Problem: AFAIK this requires flattening the reactive cursors into static arrays, so now the page won't update. (Perhaps there's a way of making the page recalculate this array when any collection changes, salving this approach?)
You create a new collection, say FeedItems
. When a new Post
or User
is created, you also create a new FeedItem
and copy the relevant information into it. Displaying the items in the client is now very straightforward. Problem: Now there is no reactivity between the canonical objects and the FeedItem
versions of them, so if someone changes their name, deletes a post, etc., this won't be reflected in the feed. (Perhaps there's a way of creating reactivity between collections to salvage this approach?)
Perhaps there's some way of sticking with the existing collections, but creating an additional 'newsFeed'
publication which would somehow merge them. I haven't seen any way of doing this, however. I see in the docs that you can publish an array of collections, but AFAIK this is equivalent to publishing the same collections one at a time.
Is one of these approaches on the right track? Or is there another I haven't thought of?
By far the easiest solution is to merge them on the client in a template helper. For example:
Template.dashboard.helpers({
peopleAndPosts: function() {
var people = People.find().fetch();
var posts = Posts.find().fetch();
var docs = people.concat(posts);
return _.sortBy(docs, function(doc) {return doc.createdAt;});
}
});
and the template (assuming people and posts have a name
):
<template name="dashboard">
<ul>
{{#each peopleAndPosts}}
<li>{{name}}</li>
{{/each}}
</ul>
</template>
This will be reactive because the helper is a reactive context so any changes to People
or Posts
will cause the returned array to be recomputed. Be aware that because you are not returning a cursor, any changes to either collection will cause the entire set to render again. This won't be a big deal if the length of the returned array is relatively short.
Idea 1 seems to work out of the box. I created a new meteor project and change it as follows:
test.js:
Users = new Meteor.Collection("users");
Posts = new Meteor.Collection("posts");
if (Meteor.isClient) {
Template.hello.array = function () {
var a = Users.find().fetch()
.concat(Posts.find().fetch());
return _.sortBy(a, function(entry) { return entry.votes; });
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Users.insert({name: "user1", votes: 1});
Users.insert({name: "user2", votes: 4});
Users.insert({name: "user3", votes: 8});
Users.insert({name: "user4", votes: 16});
Posts.insert({name: "post1", votes: 2});
Posts.insert({name: "post2", votes: 4});
Posts.insert({name: "post3", votes: 6});
Posts.insert({name: "post4", votes: 8});
});
}
test.html:
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
{{#each array}}
<div>{{votes}} {{name}}</div>
{{/each}}
</template>
This gives the expected list:
1 user1
2 post1
4 user2
4 post2
6 post3
8 user3
8 post4
16 user4
Then I did Users.insert({name: "new", votes: 5}
in the console and got (reactively):
1 user1
2 post1
4 user2
4 post2
5 new
6 post3
8 user3
8 post4
16 user4
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With