Subscriptions and publishing to client side collections


#1

Hi,

I want to be able to subscribe to a server side collection and fetch filtered documents, adding them to a named client side collection.

Let’s say I have a server side collection as below:

// Server
let entries = new Mongo.Collection('entries');

The documents inside this collection have an attribute that I want to filter by, and I want this to be the basis for syncing with my client side collections which are defined as below:

// Client
let projects = new Mongo.Collection('projects');
let categories = new Mongo.Collection('categories');
let pages = new Mongo.Collection('pages');

What I want to be able to do is use a subscription function, which takes the name of the client side collection and use that as a selector on the server side collection when publishing to filter out the documents that match.

Here is a server side publish function I have written:

Meteor.publish('projects', (filter = {}) => entriesOfType('projects', filter));

And this is the filtering function I am using to filter the returned collection, so that it only fetches entries that have a particular content type name:

let entriesOfType = (type, filter = {}) => {

	let mappedNames = [
		{collection: 'projects', contentTypeName: 'Project'},
		{collection: 'categories', contentTypeName: 'Project Category'},
		{collection: 'pages', contentTypeName: 'Page'},
		{collection: 'experiences', contentTypeName: 'Experience'}
	];

	let mappedName 	= mappedNames.find((mappedName) => mappedName.collection === type),
	    contentType 	= Collections.contentTypes.findOne({name: mappedName.contentTypeName});

	if(typeof contentType === 'undefined') return;
	let entriesSelector 	= attach({'sys.contentType.sys.id': contentType.sys.id}, filter);
	
	return Collections.entries.find(entriesSelector);
};	

What I would expect to happen here is that when I call subscribe on the client side and pass in something like ‘projects’ as a content type name, the server would then sync with the client side collection with the name of ‘projects’ and this collection would then have all entries with the content type name of project.

What is actually happening is that the client side collection with the name of ‘projects’ is empty after making a subscribe call.

Has anyone else encountered this issue and if so, how did you overcome it?


#2

I came up with an interim solution that I will continue working with to make sure it’s solid. The change here is the addition of an observer for the entries collection - paying particular attention to the added, changed and removed callbacks.

let entriesOfType = function(type, filter = {}) {

	let mappedNames = [
		{collection: 'projects', contentTypeName: 'Project'},
		{collection: 'categories', contentTypeName: 'Project Category'},
		{collection: 'pages', contentTypeName: 'Page'},
		{collection: 'experiences', contentTypeName: 'Experience'}
	],
	mappedName 				= mappedNames.find((mappedName) => mappedName.collection === type),
	contentType 			= Collections.contentTypes.findOne({name: mappedName.contentTypeName}),
	entries,
	handle;

	if(typeof contentType === 'undefined') {
		console.log(`Could not find collection of type: ${type}`);
		return;
	}

	let selector 	= attach({'sys.contentType.sys.id': contentType.sys.id}, filter);
	entries 			= Collections.entries.find(selector);

	handle = entries.observeChanges({
		added: (id, entry) => {
			this.added(type, id, entry);
		},
		changed: (id, entry) => {
			this.changed(type, id, entry);
		},
		removed: (id, entry) => {
			this.removed(type, id, entry);
		}
	});

	this.onStop(handle.stop);
	this.ready();
};

By Specifying the type in the added, changed and removed callbacks, I was able to specify the name of the client side collection as the first parameter for the respective callbacks, so when a subscription is made, the client side MiniMongo collection with that same name has the data populated inside it.