Now we have a client side application that creates and renders its own data.
So, if we were in any framework other than Meteor, we would likely start implementing a series of REST endpoints to connect the server to the client. We would also need to create a database and functions to connect to it.
And we haven't even talked about real-time, in which case we would need to add sockets, a local DB for cache and handle latency compensation (or just ignore those features and create a not-so-good or less modern app...)
But luckily, we use 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 Angular-ish 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 manipulate them.
Thanks to MiniMongo, Meteor's client-side Mongo emulator, Mongo.Collection
can be used from both client and server code.
In short, Meteor core's setup has:
Angular2-Meteor team also provides an additional package called meteor-rxjs
which wraps Meteor's original API, and returns RxJs Observable
instead of using callbacks or promises.
Observable
is very similar to Promise
, only it has a continuation flow - which means a multiple resolve
s.
The lifecycle of Observable
is built on three parts:
next
- called each time the Observable changes.error
- called on error.complete
- calls when the data flow is done.If we try to connect it to our Meteor world and the world of Mongo Collection, so each time a Collection changes - the next
callback called, and complete
should not be called because we are using reactive data and we will always waiting for more updates.
We will use this package instead of Meteor's API, because Angular 2 supports RxJS Observable
s, and provides great features for those who uses it for their application - starting from iterating on object and a faster change detection.
You can read more about Observable
s and RxJS here.
Note that RxJS documentation might be a little intimidating at the beginning - if you having difficult with it - try to read the examples we use in this tutorials and it's might help you.
So first, let's define our first parties collection that will store all our parties.
We will use MongoObservable
static methods to declare the Collection:
So add a file both/collections/parties.collection.ts
1
2
3
import { MongoObservable } from 'meteor-rxjs';
export const Parties = new MongoObservable.Collection('parties');
We've just created a file called parties.collection.ts
, that contains a CommonJS module called both/collections/parties
.
This work is done by the TypeScript compiler behind the scenes.
The TypeScript compiler converts .ts
files to ES5, then registers a CommonJS module with the same name as the relative path to the file in the app.
That's why we use the special word export
. We are telling CommonJS that we are allowing the object to be exported from this module into the outside world.
Meteor has a series of special folder names, including the client
folder. All files within a folder named client
are loaded on the client only. Likewise, files in a folder called server
are loaded on the server only.
Placing the both
folder outside of any special folder, makes its contents available to both the client and the server. Therefore, the parties
collection (and the actions on it) will run on both the client (minimongo) and the server (Mongo).
Though we only declared our model once, we have two modules that declare two versions of our parties collection: one for client-side and one for server-side. This is often referred to as "isomorphic" or "universal javascript". All synchronization between these two versions of collections is handled by Meteor.
Now that we've created the collection, our client needs to subscribe to it's changes and bind it to our this.parties
array.
Because we use MongoObservable.Collection
instead of regular Meteor Collection, Angular 2 can easily support this type of data object, and iterate it without any modifications.
Let's import the Parties
from collections:
1
2
3
4
5
6
7
import { Component } from '@angular/core';
import { Parties } from '../../../both/collections/parties.collection';
import template from './app.component.html';
@Component({
And now we will create a query on our Collection, and because we used MongoObservable
, the return value of find
will be a Observable<any[]>
- which is an Observable
that contains an array of Objects.
And let's bind to the Observable
:
1
2
3
4
5
10
11
12
13
14
15
16
17
18
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Parties } from '../../../both/collections/parties.collection';
...some lines skipped...
template
})
export class AppComponent {
parties: Observable<any[]>;
constructor() {
this.parties = Parties.find({}).zone();
}
}
We used
zone()
method which is a wrapper for the regularObservable
that do some Magic and connects the collection changes into our view using our Component'sZone
.
Because of that, we now need to add AsyncPipe
:
1
2
3
4
5
6
<div>
<ul>
<li *ngFor="let party of parties | async">
{{party.name}}
<p>{{party.description}}</p>
<p>{{party.location}}</p>
At this point we've implemented a rendering of a list of parties on the page. Now it's time to check if the code above really works; it shouldn't just render that list, but also render all changes to the database on the page reactively.
In Mongo terminology, items inside collections are called documents. So, let's insert some documents into our collection by using the server database console.
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 using Mongo shell. At the prompt, type:
db.parties.insert({ name: "A new party", description: "From the mongo console!", location: "In the DB" });
In your web browser, you will see the UI of your app immediately update to show the new party. You can see that 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({});
Choose one party you want to remove and copy its 'id' property. Then, remove it using that id (replace 'N4KzMEvtm4dYvk2TF' with your party's id value):
db.parties.remove({"_id": ObjectId("N4KzMEvtm4dYvk2TF")});
Again, you will see the UI of your app immediately updates with that party removed.
Feel free to try running more actions like updating an object from the console, and so on.
Until now we've been inserting party documents to our collection using the Mongo console.
It would be convenient though to have some initial data pre-loaded into our database.
So, let's initialize our server with the same parties as we had before.
Let's create a file server/imports/fixtures/parties.ts
and implement loadParties
method inside to load parties:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Parties } from '../../../both/collections/parties.collection';
export function loadParties() {
if (Parties.find().cursor.count() === 0) {
const parties = [{
name: 'Dubstep-Free Zone',
description: 'Can we please just for an evening not listen to dubstep.',
location: 'Palo Alto'
}, {
name: 'All dubstep all the time',
description: 'Get it on!',
location: 'Palo Alto'
}, {
name: 'Savage lounging',
description: 'Leisure suit required. And only fiercest manners.',
location: 'San Francisco'
}];
parties.forEach((party) => Parties.insert(party));
}
}
Then create main.ts
to run this method on Meteor startup:
1
2
3
4
5
6
7
import { Meteor } from 'meteor/meteor';
import { loadParties } from './imports/fixtures/parties';
Meteor.startup(() => {
loadParties();
});
To make it fully TypeScript compatible, we need to define Party
interface:
1
2
3
4
5
export interface Party {
name: string;
description: string;
location: string;
}
And add it in few places:
1
2
3
4
5
import { MongoObservable } from 'meteor-rxjs';
import { Party } from '../models/party.model';
export const Parties = new MongoObservable.Collection<Party>('parties');
2
3
4
5
6
7
8
11
12
13
14
15
16
17
import { Observable } from 'rxjs/Observable';
import { Parties } from '../../../both/collections/parties.collection';
import { Party } from '../../../both/models/party.model';
import template from './app.component.html';
...some lines skipped...
template
})
export class AppComponent {
parties: Observable<Party[]>;
constructor() {
this.parties = Parties.find({}).zone();
1
2
3
4
5
6
7
8
9
17
18
19
20
21
22
import { Parties } from '../../../both/collections/parties.collection';
import { Party } from '../../../both/models/party.model';
export function loadParties() {
if (Parties.find().cursor.count() === 0) {
const parties: Party[] = [{
name: 'Dubstep-Free Zone',
description: 'Can we please just for an evening not listen to dubstep.',
location: 'Palo Alto'
...some lines skipped...
location: 'San Francisco'
}];
parties.forEach((party: Party) => Parties.insert(party));
}
}