Now that we have the layout and some dummy data, let’s create a Meteor server and connect to it to make our app real.
First download Meteor from the Meteor site: https://www.meteor.com/
Now let’s create a new Meteor server inside our project.
Open the command line in our app’s root folder and type:
$ meteor create api
We just created a live and ready example Meteor app inside an api
folder.
As you can see Meteor
provides with an example app. Since non of it is relevant to us, you can go ahead and delete:
$ cd api
$ rm .gitignore
$ rm package.json
$ rm -rf node_modules
$ rm -rf client
$ rm -rf server
By now you probably noticed that Meteor
uses npm
, just like the our Ionic
project. Since we don't want our client and server to be seperated and require a duplicated installation for each package we decide to add, we'll need to find a way to make them share the same resource.
We will acheive that by symbolic linking the node_modules
dir:
$ cd api
$ ln -s ../node_modules
NOTE: Our symbolic link needs to be relative, otherwise it won't work on other machines cloning the project.
Don't forget to reinstall Meteor
's node dependencies after we deleted the node_modules
dir:
$ npm install meteor-node-stubs babel-runtime --save
Our package.json
should look like this:
19
20
21
22
23
24
25
"lodash.camelcase": "^4.1.1",
"lodash.upperfirst": "^4.2.0",
"script-loader": "^0.7.0",
"meteor-node-stubs": "^0.2.3",
"moment": "^2.13.0",
"webpack": "^1.13.0"
},
Now we are ready to write some server code.
Let’s define two data collections, one for our Chats
and one for their Messages
.
We will define them inside a dir called server
in the api
, since code written under this dir will be bundled only for server side by Meteor
's build system. We have no control of it and therefore we can't change this layout. This is one of Meteor
's disadvantages, that it's not configurable, so we will have to fit ourselves into this build strategy.
Let's go ahead and create the collections.js
file:
1
2
3
4
import { Mongo } from 'meteor/mongo';
export const Chats = new Mongo.Collection('chats');
export const Messages = new Mongo.Collection('messages');
Now we will update our webpack.config.js
to handle some server logic:
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
resolve: {
extensions: ['', '.js'],
alias: {
lib: __dirname + '/www/lib',
api: __dirname + '/api/server'
}
}
};
function resolveExternals(context, request, callback) {
return meteorPack(request, callback) ||
cordovaPlugin(request, callback) ||
callback();
}
function meteorPack(request, callback) {
var match = request.match(/^meteor\/(.+)$/);
var pack = match && match[1];
if (pack) {
callback(null, 'Package["' + pack + '"]' );
return true;
}
}
function cordovaPlugin(request, callback) {
var match = request.match(/^cordova\/(.+)$/);
var plugin = match && match[1];
We simply added an alias for the api/server
folder and a custom handler for resolving Meteor
packages. This gives us the effect of combining client side code with server side code, something that is already built-in in Meteor
's cli, only this time we created it.
Now that the server side is connected to the client side, we will also need to watch for changes over there and re-build our client code accordingly.
To do so, we will have to update the watched paths in the gulpfile.js
:
11
12
13
14
15
16
17
var webpackConfig = require('./webpack.config');
var paths = {
webpack: ['./src/**/*.js', '!./www/lib/**/*', './api/server/**/*.js'],
sass: ['./scss/**/*.scss']
};
Let’s bring Meteor
's powerful client side tools that will help us easily sync to the Meteor
server in real time.
Navigate the command line into your project’s root folder and type:
$ npm install meteor-client-side --save
$ npm install angular-meteor --save
Notice that we also installed angular-meteor
package which will help us bring Meteor
's benefits into an Angular
project.
Our package.json
should look like so:
4
5
6
7
8
9
10
19
20
21
22
23
24
25
26
27
28
"description": "whatsapp: An Ionic project",
"dependencies": {
"angular-ecmascript": "0.0.3",
"angular-meteor": "^1.3.11",
"babel": "^6.5.2",
"babel-core": "^6.7.6",
"babel-loader": "^6.2.4",
...some lines skipped...
"gulp-sass": "^2.0.4",
"lodash.camelcase": "^4.1.1",
"lodash.upperfirst": "^4.2.0",
"meteor-client-side": "^1.3.4",
"meteor-node-stubs": "^0.2.3",
"moment": "^2.13.0",
"script-loader": "^0.7.0",
"webpack": "^1.13.0"
},
"devDependencies": {
Don't forget to import the packages we've just installed in the index.js
file:
7
8
9
10
11
12
13
import 'script!lib/angular-ui-router/release/angular-ui-router';
import 'script!lib/ionic/js/ionic';
import 'script!lib/ionic/js/ionic-angular';
import 'script!meteor-client-side/dist/meteor-client-side.bundle';
import 'script!angular-meteor/dist/angular-meteor.bundle';
// app
import './app';
We will also need to load angular-meteor
into our app as a module dependency, since that's how Angular
's module system works:
11
12
13
14
15
16
17
const App = 'whatsapp';
Angular.module(App, [
'angular-meteor',
'ionic'
]);
Now instead of mocking a static data in the controller, we can mock it in the server.
Create a file named bootstrap.js
inside the api/server
dir and place the following initialization code inside:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import Moment from 'moment';
import { Meteor } from 'meteor/meteor';
import { Chats, Messages } from './collections';
Meteor.startup(function() {
if (Chats.find().count() !== 0) return;
Messages.remove({});
const messages = [
{
text: 'You on your way?',
timestamp: Moment().subtract(1, 'hours').toDate()
},
{
text: 'Hey, it\'s me',
timestamp: Moment().subtract(2, 'hours').toDate()
},
{
text: 'I should buy a boat',
timestamp: Moment().subtract(1, 'days').toDate()
},
{
text: 'Look at my mukluks!',
timestamp: Moment().subtract(4, 'days').toDate()
},
{
text: 'This is wicked good ice cream.',
timestamp: Moment().subtract(2, 'weeks').toDate()
}
];
messages.forEach((m) => {
Messages.insert(m);
});
const chats = [
{
name: 'Ethan Gonzalez',
picture: 'https://randomuser.me/api/portraits/thumb/men/1.jpg'
},
{
name: 'Bryan Wallace',
picture: 'https://randomuser.me/api/portraits/thumb/lego/1.jpg'
},
{
name: 'Avery Stewart',
picture: 'https://randomuser.me/api/portraits/thumb/women/1.jpg'
},
{
name: 'Katie Peterson',
picture: 'https://randomuser.me/api/portraits/thumb/women/2.jpg'
},
{
name: 'Ray Edwards',
picture: 'https://randomuser.me/api/portraits/thumb/men/2.jpg'
}
];
chats.forEach((chat) => {
const message = Messages.findOne({ chatId: { $exists: false } });
chat.lastMessage = message;
const chatId = Chats.insert(chat);
Messages.update(message._id, { $set: { chatId } });
});
});
The code is pretty easy and self explanatory.
Let’s bind the collections to our ChatsCtrl
.
We will use Scope.helpers()
, each key will be available on the template and will be updated when it changes. Read more about helpers in our API.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Chats } from 'api/collections';
import { Controller } from 'angular-ecmascript/module-helpers';
export default class ChatsCtrl extends Controller {
constructor() {
super(...arguments);
this.helpers({
data() {
return Chats.find();
}
});
}
remove(chat) {
this.data.remove(chat._id);
}
}
NOTE: These are exactly the same collections as the server's. Adding
meteor-client-side
to our project has created aMinimongo
on our client side.Minimongo
is a client side cache with exactly the same API as Mongo's API.Minimongo
will take care of syncing the data automatically with the server.NOTE:
meteor-client-side
will try to connect tolocalhost:3000
by default. To change it, simply set a global object named__meteor_runtime_config__
with a property calledDDP_DEFAULT_CONNECTION_URL
and set whatever server url you'd like to connect to.TIP: You can have a static separate front end app that works with a
Meteor
server. you can useMeteor
as a back end server to any front end app without changing anything in your app structure or build process.
Now our app with all its clients is synced with our server in real time!
To test it, you can open another browser, or another window in incognito mode, open another client side by side and delete a chat (by swiping the chat to the left and clicking delete
).
See the chat is being deleted and updated in all the connected client in real time!
{{tutorialImage 'ionic' '3.png' 500}}