Build a simple real-time application that you can use as a starter to do what you want with MEAN2(MongoDB, Express, Angular2 & NodeJS), within the project will also be Socket.ioTypescript and Angular-material2.

After having worked with Angular2 and socket.io I faced some challenges that motivated me to build a simple starter project that I will reuse every times need be.

I suppose that you already know the power of Ajax to build SPA application. But now there is a new protocol, not attached to the browser and it’s called socket.

Notes

  • This is a starter project for those who need real time in a MEAN2 app.
  • This is not a tutorial to teach you the MEAN2 stack or Socket, but to show you a way to build a working realtime app with ease
  • CRUD requests are made with rest
  • For the moment, only notifications happen with socket.io
  • Source code: https://github.com/dassiorleando/gistology(Has been recently updated to use Angular 5)

Let’s call it gistology: https://gistology.herokuapp.com 🙂

Recommandations

→ Have NodeJs and npm on your system

→ To use this article I recommend you to follow Readme instructions

 

What is used

  • NodeJS: Open-source JavaScript runtime environment built on Chrome’s V8 JavaScript engine
  • MongoDB: Database Management System
  • Express: Web framework used in nodes app for rest requests
  • Angular2: The successor of Angular1 that makes the code more easier to understand, brings a lot of good functionalities in Angular apps, simplified DI, component driven apps, enriched the interaction with the DOM and benefit of Typescript. Then Angular2 is not an MVC framework, but a component-based framework(@Component).
  • Angular CLI: Command line tool that helps to create an Angular2 project with a well structured architecture.
  • Angular Material 2: Use Angular-material in Angular2 application.
  • Typescript: is a superset of Javascript that allows us to define new types, declaring typed variables.
  • Angular2-toster: For toast messages(info, success, warning and error notifications).
  • angular-flex-layout: Better design the layouts(row and column) of our pages.
  • Socket.io(client & server): Js lib used to make sockets connections, send and receive messages via events with a simple syntaxe. We use it to connect multiple clients together and let them exchange messages(events are created and handled).
  • Webpack: A module bundler for modern javascript application, this means webpack takes modules with dependencies and emits static assets representing those modules. Here it builds all into the directory dist as defined by angular-cli defaults options.

Client Side

→ Start App Architecture: Angular CLI

First you will need to install AngularCLI, to get started here is the link.

  • Install the tool: npm install -g @angular/cli
  • Create the project: ng new gistology
  • Serve the app: ng serve (within the root folder), this command also watches your files for any changes to reload the browser.
  • More commands: ng buildng testng lint and ng e2e.

Now via your browser access the app here: http://localhost:4200

→ Generate client side components to manage gists:

Now we will still use the power of angular-cli to come out with a new component that will help us to manage gists, with this command:

ng g component gist

Output:

→ Include Angular-material2

Click here to get all steps.

  • npm install — save @angular/material
  • Imports and install

Don’t forget to also install and load @angular/animation to enable some material components to work normally.

→ Include Angular-flexLayout

  • npm install — save @angular/flex-layout
  • import {FlexLayoutModule} from “@angular/flex-layout”; in the app.module.ts

→ Design the home page: angular material card and list

/*CSS file: gistology app*/
.content{
    margin: 2em auto;
}
md-card-title{
text-align: center;
}
.footer{
position: relative;
margin-top: 50px;
bottom: 0;
}
<div fxLayout="column" fxFlex>
  <md-toolbar color="primary">
      <span class="middle">{{title}}</span>
      <span class="spacer"></span>
      <button md-icon-button [mdMenuTriggerFor]="menu">
        <md-icon>more_vert</md-icon>
      </button>
      <md-menu #menu="mdMenu">
        <button md-menu-item>
          <span>About</span>
        </button>
        <button md-menu-item>
          <span>Contact</span>
        </button>
      </md-menu>
  </md-toolbar>
<!-- intro words -->
<div class="content" fxFlex>
<div [fxLayout]="row">
<div fxFlex="20"> </div>
<div fxFlex="60">
<md-card fxFlex="100">
<md-card-title>The Best Gists Ever Seen</md-card-title>

<md-card-content>
<p>
The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan.
A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally
bred for hunting.
</p>
</md-card-content>
</md-card>
</div>
<div fxFlex="20"> </div>
</div>
</div>

<router-outlet></router-outlet>

<!-- The footer -->
<div class="footer" [fxLayout]="row">
<div fxFlex="35"> </div>
<div fxFlex="30">
<div fxFlex="100">
<div fxFlex="25"> </div>
<div fxFlex="10">
<md-icon>favorite</md-icon>
</div>
<div fxFlex="40">
By <a href="https://www.linkedin.com/in/dassi-orleando-257b04ab/">Dassi Orleando</a>
</div>
</div>
</div>
<div fxFlex="35"> </div>
</div>
</div>

→ Include Angular2-toaster for notifications

  • Install it via npm:
npm install — save angular2-toaster
  • Include his css too in the main css file(style.css):
@import ‘~angular2-toaster/toaster.css’;
  • Import and Register ToasterModule from “angular2-toaster” in the app module
  • Define the Toast container(<toaster-container></toaster-container>) in app.component.html
  • Finally import the ToastService and you will be allowed to toast with success, info, warning and error functions.
  • Create Gist client model./src/app/gist/gist.model.ts
/**
* Model for gist
*/
export class Gist{
  constructor(public title, public description, public technologies, public link)   
  {
  }
}

→ Include socket.io-client

  • npm install socket.io-client — save
  • npm install — save-dev @types/socket.io-client
  • npm install — save @types/socket.io-client — only=dev (the typings of the client socket.io)

Now we need to add this @types in ./src/tsconfig.app.json and ./src/tsconfig.spec.json in the array types.

FILE tsconfig.app.json

{
  “extends”: “../tsconfig.json”,
  “compilerOptions”: {
    “outDir”: “../out-tsc/app”,
    “module”: “es2015”,
    “baseUrl”: “”,
    “types”: [“socket.io-client”]
  },
  “exclude”: [
    “test.ts”,
    “**/*.spec.ts”
  ]
}

Install it is very easy, we just have a simple command to type. Now we can create a simple service to emit and handle socket events, lets call it app.socketIo.service.ts in folder ./src/app:

import { Injectable } from '@angular/core';
import {Gist} from './gist/gist.model';
import * as io from 'socket.io-client';
import {ToasterService} from 'angular2-toaster';
@Injectable()
export class AppSocketIoService {
private socket: SocketIOClient.Socket; // The client instance of socket.io

// Constructor with an injection of ToastService
constructor(private toasterService: ToasterService) {
this.socket = io();
}

// Emit: gist saved event
emitEventOnGistSaved(gistSaved){
this.socket.emit('gistSaved', gistSaved);
}

// Emit: gist updated event
emitEventOnGistUpdated(gistUpdated){
this.socket.emit('gistUpdated', gistUpdated);
}

// Consume: on gist saved
consumeEvenOnGistSaved(){
var self = this;
this.socket.on('gistSaved', function(gist: Gist){
self.toasterService.pop('success', 'NEW GIST SAVED',
'A gist with title \"' + gist.title + '\" has just been shared' + ' with stack: ' + gist.technologies);
});
}

// Consume on gist updated
consumeEvenOnGistUpdated(){
var self = this;
this.socket.on('gistUpdated', function(gist: Gist){
self.toasterService.pop('info', 'GIST UPDATED',
'A gist with title \"' + gist.title + '\" has just been updated');
});
}
}

Server Side

→ Install packages

  • Express: to serve Web App
  • body-parser: to parse post requests and get data from html form
  • http-status: to explicitly describe status of requests
  • Mongoose: to connect to a mongo database
  • Socket.io-server: Server side lib of socket.io
npm install --save express body-parser http-status mongoose socket.io
  • Update Architecture

Update file architecture for the server side code to add model and api.

Create a file we will use to run our application, we can call it index.js on the root folder. The trick here is to serve statics files as expected, let the /api root path for our api and all others request will be redirect to the index file present in dist folder when running the app; without forgetting to instantiate the socket.io server and handle some events to make sure all clients get them. The mongoose lib here will connect us to a mongo DB and allows us to define the server model.

In the index.js file we have:

// Get dependencies
const express = require('express');
const path = require('path');
const http = require('http');
const bodyParser = require('body-parser');
// Get gist route
const api = require('./server/routes/api');
const app = express();
/**
 * Create HTTP server.
 */
const server = http.createServer(app);
// Socket.io for real time communication
var io = require('socket.io').listen(server);
// Parsers for POST data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// Point static path to dist
app.use(express.static(path.join(__dirname, 'dist')));

// Set our api routes
app.use('/api', api);

// Catch all other routes and return the index file
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});

/**
* Get port from environment and store in Express.
*/
const port = process.env.PORT || '3000';
app.set('port', port);

/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port, () => console.log(`API running on localhost:${port}`));

/**
* Socket events
*/
io.sockets.on('connection', function(socket){
console.log('Socket connected');
// Socket event for gist created
socket.on('gistSaved', function(gistSaved){
io.emit('gistSaved', gistSaved);
});

// Socket event for gist updated
socket.on('gistUpdated', function(gistUpdated){
io.emit('gistUpdated', gistUpdated);
});
});

→ Gist model: ./server/models/gist.js

Let’s do something really simple, all gists with a model with follow properties: titledescriptiontechnologies and the link.

// Let's create the gist schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// create a schema
var gistSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
technologies: [{
type: String
}],
link: {
type: String,
required: true
},
created_at: Date,
updated_at: Date
});

// on every save, add the date and edit updated date
gistSchema.pre('save', function(next) {
// The current date
var currentDate = new Date();

// edit the updated_at field to the current date
this.updated_at = currentDate;

// if created_at doesn't exist, add to that field
if (!this.created_at)
this.created_at = currentDate;

next();
});

// Create a model from the schema
var Gist = mongoose.model('Gist', gistSchema);

// Exports it to be abailable in all the application
module.exports = Gist;

→ To import mongoose and pass the db name to connect

var mongoose = require(‘mongoose’);
mongoose.connect(‘mongodb://localhost/gistology’); // A local db is used(gistology)

→ Build a little rest api for gists:

  • File api.js presents in folder ./server/routes
const express = require('express');
const router = express.Router();
var status = require('http-status');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/gistology');
var Gist = require('../models/gist');
/* GET a gist by his ID. */
router.get('/gist/:gistId', (req, res) => {
var gistId = req.params.gistId;

// Check first if it is a valid Id
if (!mongoose.Types.ObjectId.isValid(gistId)) {
return res.status(400).send({
message: 'Gist Id is invalid'
});
}

Gist.findById(gistId, function(err, gistFounded) {
if (err) return res.status(status.BAD_REQUEST).json(err);
// We serve as json the gist founded
res.status(status.OK).json(gistFounded);
});
});

/* PUT: update a new gist */
router.put('/gist', (req, res) => {
var data = req.body;
var id = data._id;

// Properties to update on an exiting gist
var gistToUpdate = {
title: data.title,
description: data.description,
technologies: data.technologies,
link: data.link
};

// find the gist with id :id
Gist.findByIdAndUpdate(id, gistToUpdate, function(err, gist) {
if (err) return res.status(status.BAD_REQUEST).json(err);

// The gist has been updated
res.status(status.OK).json(gistToUpdate);
});
});

/* POST: save a new gist */
router.post('/gist', (req, res) => {
var data = req.body;
// create a new gist
var newGist = Gist({
title: data.title,
description: data.description,
technologies: data.technologies,
link: data.link
});

// save the gist
newGist.save(function(err, gist) {
if (err) return res.status(status.BAD_REQUEST).json(err);
res.status(status.OK).json(gist);
});
});

/* GET all saved gists */
router.get('/gist', (req, res) => {
Gist.find({}, function(err, gists) {
if (err) return res.status(status.BAD_REQUEST).json(err);

// object of all the gists
res.status(status.OK).json(gists);
});
});

/* DELETE: delete a gist by id */
router.delete('/gist/:gistId', (req, res) => {
var gistId = req.params.gistId;

// find the gist by id and remove it
Gist.findByIdAndRemove(gistId, function(err) {
if (err) return res.status(status.BAD_REQUEST).json(err);

// The gist has been deleted
res.status(status.OK).json({message: 'SUCCESS'});
});
});

module.exports = router;
  • Import the api file
// Get gist route
const api = require(‘./server/routes/api’);
  • Defined /api as root path for all our api and match with the api.js file

Note: in the index.js file up there we done something like that.

→ Test the project:

Test the API: curl http://localhost:3000/api/gist returns an empty array([]) as I don’t yet create any gist(Yes the db is still empty)

Setup and run Socket.io Server:

File: ./index.js

const express = require(‘express’);
const http = require(‘http’);
const app = express();
/**
* Create HTTP server and start the connect the socket
*/
const server = http.createServer(app);
// Socket.io for real time communication
var io = require(‘socket.io’).listen(server);
  • Emit & Handle events
/**
* Socket events
*/
io.sockets.on(‘connection’, function(socket){
  console.log(‘Socket connected’);
  // Socket event for gist created
  socket.on(‘gistSaved’, function(gistSaved){
    io.emit(‘gistSaved’, gistSaved);
  });
  // Socket event for gist updated
  socket.on(‘gistUpdated’, function(gistUpdated){
    io.emit(‘gistUpdated’, gistUpdated);
  });
});

Handle gist rest requests with @angular/http & rxjs

File: ./src/app/gist/gist.service.ts

  • Source code
import { Gist } from ‘./gist.model’;
import { Injectable } from ‘@angular/core’;
import { Http } from ‘@angular/http’;
import ‘rxjs/add/operator/map’;
@Injectable()
export class GistService {
  constructor(private http: Http) {}
  // Get all saved gists
  getAllGists(){
    return this.http.get(‘/api/gist’)
      .map(res => res.json());
  }
  // Get a gist by Id
  getGistById(gistId){
    return this.http.get(‘/api/gist/’ + gistId)
      .map(res => res.json());
  }
  // register a new gist
  postGist(gist: Gist){
    return this.http.post(‘/api/gist’, gist)
      .map(res => res.json());
  }
  // update a gist
  updateGist(gist: Gist){
    return this.http.put(‘/api/gist’, gist)
      .map(res => res.json());
  }
}
  • Register the service in the AppModule
// Some imports …
import { GistService } from ‘./gist/gist.service’;
@NgModule({
  declarations: […],
  imports: […],
  providers: [GistService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Ui to manage Gist: list, create, show and delete


Dassi Orleando

Dassi Orleando

Full-Stack developer by formation and profession, fixer, driver, entrepreneur, freelancer and passionate about all around IT, I love implement or use different things all the days. If you would like to contact or follow me, you can do so below.

Related Posts

AngularJS

AngularJs’s global objects instead of native ?

Overview AngularJs is one of the widely used open sources front-end framework because of its good architecture in place, the extensibility, a lot of interesting features and the big community too. In this short article, we're Read more...

Shares