We now have a nice client side application that creates and renders its own data.
If we were in any framework other than Meteor, we would start implementing a series of REST endpoints to connect the server to the client. Also, we would need to create a database and functions to connect to it.
And we haven't talked about realtime, in which case we would need to add sockets, and a local DB for cache and handle latency compensation (or just ignore those features and create a not - so - good and modern app...)
But luckily, we're using Meteor!
Meteor makes writing distributed client code as simple as talking to a local database.
Every Meteor client includes an in-memory database cache. To manage the client cache, the server publishes sets of JSON documents, and the client subscribes to these sets. As documents in a set change, the server patches each client's cache automatically.
That introduces us to a new concept - Full Stack Reactivity.
In an Angularish language we might call it 3 way data binding.
The way to handle data in Meteor is through the Mongo.Collection
class. It is used to declare MongoDB collections and to manipulate them.
Thanks to minimongo, Meteor's client-side Mongo emulator, Mongo.Collection
can be used from both client and server code.
So first, let's define the parties collection that will store all our parties.
Create new file, like this:
1
2
import { Mongo } from 'meteor/mongo';
export const Parties = new Mongo.Collection('parties');
Note that the
Mongo.Collection
has been used in a file that is outside /client or /server folders. This is because we want this file to be loaded in both client and server, and AngularJS files are loaded in the client side only.That means that this collection and the actions on it will run both on the client (minimongo) and the server (Mongo), you only have to write it once, and Meteor will take care of syncing both of them.
Now that we've created the collection, our client needs to subscribe to its changes and bind it to our parties array.
To bind them we are going to use the built-in angular-meteor feature called helpers.
Those of you who used Meteor before, should be familiar with the concept of Helpers - these are definitions that will be available in the view, and will also have reactive.
We are going to replace the declaration of $scope.parties
with the following command inside the PartiesListCtrl
controller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import { Parties } from '../collections/parties';
angular.module('socially', [
angularMeteor
])
.controller('PartiesListCtrl', function($scope) {
'ngInject';
$scope.helpers({
parties() {
return Parties.find({});
}
});
});
This line declares a new $scope.parties
variable (so we don't need to do something like $scope.parties = [];
) and then binds it to the Parties Mongo collection.
So you can access that $scope.parties
exactly like you did before.
In this example, we return a MongoDB Cursor (the return value of find
), which is a function, and Angular-Meteor wraps it as array, so when we will use $scope.parties
(in view or in a controller) - it would be a regular JavaScript array!
A helper
could be a function or any other variable type.
Example for two helpers with relationship: function that fetches search results (function helper) and string that used as search parameter (string helper). we will show more of those examples in the next chapters of the tutorials.
Now every change that happens to the $scope.parties
variable will automatically be saved to the local minimongo and synced to the MongoDB server DB and all the other clients in realtime!
But we still don't have data in that collection, so let's add some by initializing our server with the same parties we had before.
Let's create a file named main.js
in the server folder, and add this content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Meteor } from 'meteor/meteor';
import { Parties } from '../collections/parties';
Meteor.startup(() => {
if (Parties.find().count() === 0) {
const parties = [{
'name': 'Dubstep-Free Zone',
'description': 'Fast just got faster with Nexus S.'
}, {
'name': 'All dubstep all the time',
'description': 'Get it on!'
}, {
'name': 'Savage lounging',
'description': 'Leisure suit required. And only fiercest manners.'
}];
parties.forEach((party) => {
Parties.insert(party)
});
}
});
As you can probably understand, this code runs only on the server, and when Meteor starts it initializes the DB with these sample parties.
Run the app and you should see the list of parties on the screen.
In the next chapter we will see how easy it is to manipulate the data, save and publish changes to the server, and how by doing so, all the connected clients will automatically get updated.
Items inside collections are called documents. Let's use the server database console to insert some documents into our collection. In a new terminal tab, go to your app directory and type:
meteor mongo
This opens a console into your app's local development database. At the prompt, type:
db.parties.insert({ name: "A new party", description: "From the mongo console!" });
In your web browser, you will see the UI of your app immediately update to show the new party. As you see we didn't have to write any code to connect the server-side database to our front-end code — it just happened automatically.
Insert a few more parties from the database console with different text.
Now let's do the same but with remove. At the prompt, type the following command to look at all the parties and their properties:
db.parties.find();
Now choose one party you want to remove and copy it's id
property.
Remove it using that id (replace N4KzMEvtm4dYvk2TF
with your party's id value):
db.parties.remove( {"_id": "N4KzMEvtm4dYvk2TF"});
Again, you will see the UI of your app immediately update with that party removed.
Try running more actions like updating an object from the console and so on. Check out the mongodb documentation to explore the mongodb shell.
As a best practice, and as preparation for the future Angular 2.0, we recommend use controllerAs
syntax, you can find more information about why and how to use it in JohnPapa's styleguide
So first, let's give a name to our controller in the view:
1
2
3
4
5
6
<div ng-controller="PartiesListCtrl as vm">
<ul>
<li ng-repeat="party in vm.parties">
{{party.name}}
<p>{{party.description}}</p>
</li>
Great, now we need to make some changes in the implementation of the controller - the first step is use this
instead of $scope
inside the controller.
We also need to call $reactive now and attach the $scope
in order to declare and extend the controller, and turn it to Reactive controller.
You do not need to do this when using just
$scope
withoutcontrollerAs
because Angular-Meteor does this for you.
5
6
7
8
9
10
11
12
13
14
15
16
angular.module('socially', [
angularMeteor
])
.controller('PartiesListCtrl', function($scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.helpers({
parties() {
return Parties.find({});
}
Great! Now let's move on and improve our code skills even more!
In order to use best practices we will implement our controller inside an Component.
A Component is a regular directive, with controller and with specific logic that connects the view and the controller logic into one HTML tag.
This is a better pattern that let's you reuse code more easily but you can continue using your current way of writing Angular apps because the controller code stays the same.
You can find some more information about this approach here.
First, let's create a template for our Component:
1
2
3
4
5
6
<ul>
<li ng-repeat="party in partiesList.parties">
{{party.name}}
<p>{{party.description}}</p>
</li>
</ul>
main.html
ng-controller
because we no longer need itvm
to partiesList
which we defined in the Component heremain.html
Now, let's convert our Controller into a Component:
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
angular.module('socially', [
angularMeteor
])
.component('partiesList', {
templateUrl: 'client/partiesList.html',
controllerAs: 'partiesList',
controller($scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.helpers({
parties() {
return Parties.find({});
}
});
}
});
component
method to define new ComponenttemplateUrl
controllerAs
controller
In Angular 1.5 there is a new
component
function that is not exactly the same thing as adirective
. You should read more about it in the official guide.
We can now just delete the code in main.html
and instead call out Component:
1
2
3
<body ng-app="socially" ng-strict-di="">
<parties-list></parties-list>
</body>
That's it!
Now we can use <parties-list>
tag anywhere, and we will get the list of parties!
Since we're serious developers and Meteor gives us ability to use things from the future, let's move Socially to the next level!
To make Socially easier to maintain we can use es6 classes and modules.
Let's create an imports
folder. It allows us to lazy-load modules.
To fully move PartiesList component we have to create space for it.
This is a structure of the Socially app:
client/
imports/
ui/
components/ - put here every component
partiesList/
.../
server/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import angular from 'angular';
import angularMeteor from 'angular-meteor';
class PartiesList {
constructor($scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.helpers({
parties() {
return Parties.find({});
}
});
}
}
const name = 'partiesList';
// create a module
export default angular.module(name, [
angularMeteor
]).component(name, {
templateUrl: `imports/ui/components/${name}/${name}.html`,
controllerAs: name,
controller: PartiesList
});
1
2
3
4
5
6
<ul>
<li ng-repeat="party in partiesList.parties">
{{party.name}}
<p>{{party.description}}</p>
</li>
</ul>
templateUrl
Because PartiesList
module is in imports
and it's lazy-loaded we have to import it into Socially:
2
3
4
5
6
7
8
9
10
import angularMeteor from 'angular-meteor';
import { Parties } from '../collections/parties';
import { name as PartiesList } from '../imports/ui/components/partiesList/partiesList';
angular.module('socially', [
angularMeteor,
PartiesList
]);
As you can see in your browser, template is missing. It's also lazy-loaded.
1
2
3
4
5
6
7
8
23
24
25
26
27
28
29
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import templateUrl from './partiesList.html';
class PartiesList {
constructor($scope, $reactive) {
'ngInject';
...some lines skipped...
export default angular.module(name, [
angularMeteor
]).component(name, {
templateUrl,
controllerAs: name,
controller: PartiesList
});
Let me explain to you what happened there.
You could just import the html file but with the latest version of angular-templates
it's possible to get a full path of a used file. See the example below:
// You're inside: imports/ui/button.js
import templateUrl from './button.html';
console.log(templateUrl); // outputs: imports/ui/button.html
There is no component without a template so instead of asynchronously loading an html file we can use urigo:static-templates
package. It allows you to import template as a string.
Okay, so first step will be to remove angular-templates
:
$ meteor remove angular-templates
Then we can add urigo:static-templates
package:
$ meteor add urigo:static-templates
Let me show you how it works:
import template from './button.html';
console.log(template); // outputs: contents of button.html as a minified string
Okay, now you understand what's going on, so we can move on and implement it inside PartiesList:
1
2
3
4
5
6
7
23
24
25
26
27
28
29
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import template from './partiesList.html';
class PartiesList {
constructor($scope, $reactive) {
...some lines skipped...
export default angular.module(name, [
angularMeteor
]).component(name, {
template,
controllerAs: name,
controller: PartiesList
});
Since we want to use components in Socially we still have to create a main component, just like we had a main controller.
Let's do the same steps as we did with PartiesList.
1
<parties-list></parties-list>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import template from './socially.html';
import { name as PartiesList } from '../partiesList/partiesList';
class Socially {}
const name = 'socially';
// create a module
export default angular.module(name, [
angularMeteor,
PartiesList
]).component(name, {
template,
controllerAs: name,
controller: Socially
});
Now we can update main view in index.html file and load Socially in main.js
1
2
3
<body ng-app="socially" ng-strict-di="">
<socially></socially>
</body>
Here, change the main.js file like this : (remove "import { name as PartiesList } ..." and "angular.module('socially', [...")
2
3
4
5
import angularMeteor from 'angular-meteor';
import { Parties } from '../collections/parties';
import { name as Socially } from '../imports/ui/components/socially/socially';
In this chapter you saw how easy and fast it is to create a full connection between our client data, the server and all the other connected clients.
Also, we improved our code quality and used AngularJS best practices.
In the next step, we'll see how to add functionality to our app's UI so that we can add parties without using the database console.