Coll, Pub & Sub* with Angular Meteor

·

11 min read

Image by Jessica Spengler

*Collections, Publications and Subscriptions

update: Angular-Meteor syntax was dramatically changed in v1.3. The article was revised for the new syntax and concepts

In an earlier post I looked at Angular-Meteor and how it complement with Angular on one side, and Meteor on the other (or vice versa?) Let’s explain that a bit further and understand how we should work with data in Angular-Meteor.

If you look at the architecture diagram in the early post you will realize that the data resides on the server (well, obviously) and part of it is synced to the client. This is the Meteor “magic” (and fits with its principles).

Update: the sources below are still valuable, but as of Feb 2016 — you better jump directly to the meteor guide here

But understanding how this syncing works, is not always the easiest thing to get . Dan Dascalescu did a great job in explaining the Meteor collections, publish and subscribe mechanism in this article. Also, DiscoverMeteor has a great chapter on publication and subscriptions (and you can see a more advanced explanation further down the book). Seriously, you should read them.

As of Angular, I will assume that if you are here you already know (and love) the Angularjs two way data binding. Angular 1 that is.

On one side of the field we have Meteor / Mongodb that knows all the data, stores it and synchronized it between the client and the server using minimongo; and on the other side we have Angular that knows how to bind client side data between the controllers and the templates using $scope variables. Angular-Meteor serves as the glue between those layers by binding $scope variables with Meteor reactive sources. Here is what it looks like:

OK, but how?

Let’s get a bit deeper and make it alive. Here is what the programmer (that is you!) is doing in order to make the magic work:

  • Define the persistent data source on the server, and its reflection on the client.
  • Tell meteor what data the server should push to each and every client, according to what data they can see and want to see — this is the publication.
  • Tell the local client to start pulling some data from a server’s publication to a local collection — this is the subscription.
  • Bind Angular variables to the local collection to show it in the client views

Here is the visual representation of the whole flow:

And now in more details…

Mongo Database Collection

Meteor uses MongoDB on the server for its persistence (saving the data) . The MongoDB equivalent of tables and rows from the SQL world is collections and documents. Modelling collections in a noSQL DB is outside of the scope of this article, but the MongoDB documentation has some great articles about it.

In SQL world you would have to define your table in advance in order to use in your DB queries. This is not required when working with Meteor, as collections will be created automatically if they cannot be found.

We do need, however, to tell Meteor that we are referencing a collection in the MongoDB. This is how we do it in the server code:

Todos = new Mongo.Collection("todos");

This defines a server global variable named “Todos” which holds a MongoDB collection of documents called “todos”.

Client Database Collection

Meteor let’s you automatically synchronize the data between your client Database and your server’s database. This is being done by defining a collection on the client. Here is how you do it on the client:

Todos = new Mongo.Collection("todos");

Hold on! This looks very familiar. Isn’t it what we just did on the server? well yes. This part of Meteor greatness called isomorphic code. The same code can run on the server and on the client. So why should we write it twice? instead, we are going to place this code outside the “server” or the “client” folders, and in other folder that runs both on the client and on the server. We can call it, for example, common.

Server’s Publications

All is well. We have a client side and a server side collections which hold the same type of documents: “todos”. How do we get data from one to the other?

When you initially install Meteor it automatically adds the autopublish package; this package sends all the data from the server to all clients and changes made by all the clients back to server (and again to the clients). This is great if you are happy with having all your data everywhere all the time.

A real application will probably not want to do it. You will want users to see only their data or only the data relevant to them. For this, you will need to remove the autopublish package and start managing the publications yourself.

Without the autopublish package, each client will have to subscribe to the server data to receive the data they need. The first part is to create a server’s publication that is sending the data out to clients.

Meteor.publish('allTodos', function(){
return Todos.find({});
// the server will publish all the ToDos in the database

});

The above publication sends all the documents in the Todos collection to all the clients. We can limit it so each user will only get their own documents (you do not need to send the user Id as the server knows which client is requesting the data using the this.userId parameter):

Meteor.publish('myTodos', function(){
return Todos.find({owner: this.userId});
// the server will only publish the Todos that belong to the requesting user

});

But what if you have millions of Todos, but the user only want to see a small portion of them? you can send parameters that will limit the subscription only to that project, by sending the project Name (or Id) as a parameter. Here is how it is done:

Meteor.publish('projectTodos', function(project){
return Todos.find({projectName: project});
//the server will only publish the ToDos that belong to that project
});

Please note that all of the above publications can reside on the server at the same time, and each client may subscribe to any of them, using their names (allTodos, myTodos and projectTodos).

But — there is an important gotcha here, that we will discuss in the client subscriptions part in a short while.

Client’s Subscriptions

The Meteor side and why we need Angular-Meteor

So — how do we subscribe to a server’s publication? In pure meteor you can use the Meteor.subscribe function. You will need to send the subscription name and additional parameters if required. The subscribe method is described in the meteor documentation. The subscribe function receives additional parameters to the subscription.

var handle = Meteor.subscribe('projectTodos', "SpaceX"..., function () {});

The last parameter is a callback that will be called when the subscription is ready (you can send an object with callbacks that will be called when the subscription is ready or stopped. Meteor documentation and guide explain this further.

However, using Meteor subscription function is coming short in two very common scenarios:

  • We want our subscription parameters to change when something happens in the UI — you move to a new state or your user is keying or selecting something in the application.
  • We need to stop the subscription when you are moving to another route in your application (and you destroy the angular scope).

Let’s explore them one by one:

Make subscriptions UI aware and vice versa

You guessed it — Angular-Meteor comes to the rescue introducing the “Reactive Context”. Simply put — the reactive context means that your angular changes and your meteor changes are now working in sync. Or in even simpler words — if something changes on your UI— it will be reflected in Meteor and if something changes in Meteor, your UI will see it.

To make your controller scope reactive, you should inject the $reactive service provided by Angular-Meteor into your controller. If you are using $scope, it will become reactive. If you are using this or a variable set as “this” (such as “vm”), you should explicitly make it reactive by calling the $reactive.attach as follow:

myModule.controller(‘MyCtrl’, [‘$scope’, ‘$reactive’, function($scope, $reactive) {
$reactive(this).attach($scope);
}]);

From this point onward, variables defined on $scope can be used as reactive variables and used to make your subscriptions dynamic.

myModule.controller(‘MyCtrl’, [‘$scope’, ‘$reactive’, function($scope, $reactive) {

$scope.projectName = "SpaceX";
$scope.subscribe ('projectTodos', function (){
return [$scope.getReactively ("projectName");]
});
}]);

$scope.getReactively? what does that do? Well, it tells Angular to watch changes in the $scope.projectName variable and rerun the subscription every time it changes.

“SpaceX” is the project name. If you will change it via the UI (e.g. using ng-model) or by assignment (using $scope.projectName = “Tesla”), the subscription will update and return the new todos items associated with the new project.

ProTip: If you have an array of parameters to watch, You can use the $scope. getCollectionReactively method instead:

myModule.controller(‘MyCtrl’, [‘$scope’, ‘$reactive’, function($scope, $reactive) {
$scope.projectNames = ["SpaceX", "Tesla"];
$scope.subscribe ('projectTodos', function (){
return [$scope.getCollectionReactively ("projectNames");]
});
}]);

Auto stopping subscriptions

Have you noticed that we did not assign a handler to the $scope.subscribe method? you can surely do it, but Angular-Meteor is handling (ah!) it for you. When the controller will be destroyed, Angular-Meteor will ensure that the subscription is stopped, so no more data exchange with the server on that subscription will continue.

Of course, if you want to manage it manually, you can still do so:

var handler = $scope.subscribe('projectTodos', "SpaceX");

// once you no longer need the data use:
handler.stop();

Collections

The real essence of Angular-Meteor

Now the data is synced with our local minimongo collection , according to the publication and subscription. That is great, but something is still missing: How do I link that data with my HTML view? what should I put in my ng-repeat?

This is where helpers are coming into the game. Again, with the help of the great Angular-Meteor gang. In version 1.3 of Angular-Meteor (not to be confused with Meteor 1.3, which at the time of writing is in beta), Angular-Meteor was aligned with the core Meteor functionality of Helpers.

In Meteor, helpers are used to get Data from your minimongo collection into your templates. In Angular-Meteor, helpers bring the data into your controller’s scope, and from there, following the normal Angular path into your view with the beloved 2 ways data binding. Here is how:

$scope.helpers ({

todos: function (){ return Todos.find ({});
}
});

What is going on here? This binds the $scope.todos to everything that exists in the Todos local collection on the client. Under the hood it is important to know that Meteor Todos.find returns a Mongo Cursor which is constantly changing, when your data is updated. In order to make it available for the Angular ng-repeat it needs to be transformed into an array that will be updated when the underlying cursor changes.

Assume you just want a subset of the Todos: you can set a query to get only part of the items (alternatively, you can use angular filters to achieve similar result, but this may be less performant).

$scope.helpers ({

todos: function (){ return Todos.find ({}, {sort: {created: -1}, limit: 10});
}
});

This will bind the $scope.todos to the newest 10 todos. Here is an important thing to understand: this function does not impact what is stored in the local minimongo collection, it just fetches a subset of it according to the function results. That is, even if our local collection has 100 items in Todos, only the newest 10 will be included in $scope.todos.

The Todos.find ({}, {sort: {created: -1}, limit: 10}) is the Minimongo syntax to fetch the data. The minimongo syntax is the same as the MongoDB, however, some functions supported by MongoDB are not supported in minimongo.

The Important Gotcha

We did not forget it!

Here is something that is often missed: Meteor collapses all documents from the same MongoDb collection to a single minimongo collection. So take the example that you are subscribing at the same time to 2 separate publications on the ‘todos’ collection:

$scope.subscribe ('projectTodos', function (){
return ["SpaceX"]
});
$scope.subscribe ('projectTodos', function (){
return ["Tesla"]
});

At this point your local minimongo collection will hold all the todos of both “SpaceX and “Tesla” project.

Now you are binding your $scope.todos to everything in the collection as below:

$scope.helpers ({

todos: function (){ return Todos.find ({});
}
});

Your $scope.todos will have both projects’ todo lists! You will need to filter your todos in the helper functions to get just the items that you want:

$scope.helpers ({

todosProject1: function (){ return Todos.find ({"projectName: 'SpaceX'"});
},

todosProject2: function (){ return Todos.find ({"projectName: 'Tesla'"});
}});

I created a demo to show working with multiple subscriptions on the same mongo collection with Angular-Meteor. Check out at the example in http://ng-leaderboard-subs2.meteor.com/ and see the code here: https://github.com/Tallyb/ng-leaderboard-subs2

When the going gets tough…

Some common questions and issues you may hit upon…

  • Helpers are needed when returning a cursor which needs to be transformed into an array. That is — when you use the find() function on the cursor. If you need a single object — you can simply define it with a returning function that may use getReactively.
  • If you need to change the cursor items before made available for the $scope variables, you can use a transform function on the find method as described in the Meteor documentation. See the example in the leaderboards code.

Last Heroic Words — why bother?

If you love Angular and you are impressed with what Meteor allows you to do, combining them both is a winner. Need more reasons to work on both? here are some.