AngularJS tutorial for beginners with NodeJS ExpressJS and MongoDB (Part I)

AngularJS tutorial for beginners with NodeJS ExpressJS and MongoDB (Part I)

This tutorial is meant to be as clear as possible. At the same time, we are going to cover the concepts that you will need most of the time. All the good stuff without the fat :)

This tutorial is meant to be as clear as possible. At the same time, we are going to cover the concepts that you will need most of the time. All the good stuff without the fat :)

MEAN Stack tutorial series:

  1. AngularJS tutorial for beginners (Part I) 👈 you are here
  2. Creating RESTful APIs with NodeJS and MongoDB Tutorial (Part II)
  3. MEAN Stack Tutorial: MongoDB, ExpressJS, AngularJS and NodeJS (Part III)

We are going to start building all the examples in a single HTML file! It embedded JavaScript and NO styles/CSS for simplicity. Don’t worry, in the next tutorials, we will learn how to split use Angular modules. We are going to break down the code, add testing to it and styles.

1. What is Angular.js?

Angular.js is a MVW (Model-View-Whatever) open-source JavaScript web framework that facilitates the creation of single-page applications (SPA) and data-driven apps.

1.1 AngularJS vs jQuery vs BackboneJS vs EmberJS

TL; DR: AngularJS is awesome for building testable single page applications (SPA). Also, excel with data-driven and CRUD apps. Show me the code!.

AngularJS motto is

HTML enhanced for web apps!
It extends standard HTML tags and properties to bind events and data into it using JavaScript. It has a different approach to other libraries. jQuery, Backbone.Js, Ember.js and similar… they are more leaned towards “Unobtrusive JavaScript”.

Traditional JavaScript frameworks, use IDs and classes in the elements. That gives the advantage of separating structure (HTML) from behavior (Javascript). Yet, it does not do any better on code complexity and readability. Angular instead declares the event handlers right in the element that they act upon.

Times have changed since then. Let’s examine how AngularJS tries to ease code complexity and readability:

  • Unit testing ready: JavaScript is, usually, hard to unit test. When you have DOM manipulations and business logic together (e.g. jQuery based code). AngularJS keeps DOM manipulation in the HTML and business logic separated. Data and dependencies are $injected as needed.
  • DOM manipulation where they are used. It decouples DOM manipulation from application logic.
  • AngularJS is also excellent for single-page applications (SPA).
  • Different browsers implement features differently, but fret not. Angular’s directive (or HTML extensions) take care of the differences for you.
  • Global namespace expressions and methods definitions are scoped within controllers. So, they do not pollute the global namespace.
  • Data models are plain old JavaScript objects (POJO).
  • Write less code: AngualarJS features save you from much boilerplate code.
  • AngularJS provides solutions for writing modular code and dependencies management.

Without further ado, let’s dive in!

2. AngularJS Main Components

AngularJS has an extensive API and components. In this tutorial we are going to focus on the most important ones, such as directives, modules, services, controllers and related concepts.

2.1 AngularJS Directives

The first concept you need to know about AngularJS is what are directives.

Directives are extensions of HTML markups. They could take the form of attributes, element names, CSS class and or even HTML comments. When the AngularJS framework is loaded, everything inside ng-app it’s compiled. The directives are bound to data, events, and DOM transformations.

Notice in the following example that there are two directives: ng-app and ng-model.

Notice in the following example that there are two directives: ng-app and ng-model.

Hello World in AngularJS
<html ng-app>
<head>
  <title>Hello World in AngularJS</title>
</head>
<body>

<input ng-model="name"> Hello {{ name }}

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
</body>
</html>

We going to learn about some of the main built-in directives as we go:

  • ng-app: is a directive that bootstraps AngularJS. It designates the caller element as the root. It’s usually placed on <html> or <body>.
  • ng-model: is a directive that binds usually form elements. For instance, input, select, checkboxes, textarea. They keep data (model) and visual elements (HTML) in sync.
  • {{ name }} {{ }} are a way of binding models to elements in HTML. In the example above, the ng-model name is bound to the placeholder {{ name }}. Play with the example below to see how the placeholder is updated real-time to whatever you type in the textbox.

Data binding AngularJS example:

You can create your own directives. Checkout the this tutorial for more: creating-custom-angularjs-directives-for-beginners. It will go deeper into directives.

2.2 AngularJS Data Binding

Data binding is an AngularJS feature that synchronizes your model data with your HTML. That’s great because models are the “single source of truth”. You do not have to worry about updating them. Here’s a graph from docs.angularjs.org.

Whenever the HTML is changed, the model gets updated. Wherever the model gets updated it is reflected in HTML.

2.3 AngularJS Scope

$scope it is an object that contains all the data to which HTML is bound. They are the glue your javascript code (controllers) and the view (HTML). Everything that is attached to the $scope, it is $watched by AngularJS and updated.

Scopes can be bound to javascript functions. Also, you could have more than one $scope and inherit from outer ones. More on this, in the controller’s section.

2.4 AngularJS Controllers

Angular.js controllers are code that “controls” certain sections containing DOM elements. They encapsulate the behavior, callbacks and glue $scope models with views. Let’s see an example to drive the concept home:

AngularJS Controller Example
 <body ng-controller="TodoController">
  <ul>
    <li ng-repeat="todo in todos">
      <input type="checkbox" ng-model="todo.completed">
      {% raw  %}{{ todo.name }}{% endraw %}
    </li>
  </ul>

  <script>
    function TodoController($scope){
      $scope.todos = [
        { name: 'Master HTML/CSS/Javascript', completed: true },
        { name: 'Learn AngularJS', completed: false },
        { name: 'Build NodeJS backend', completed: false },
        { name: 'Get started with ExpressJS', completed: false },
        { name: 'Setup MongoDB database', completed: false },
        { name: 'Be awesome!', completed: false },
      ]
    }
  </script>
</body>

AngularJS controller interactive example:

As you might notice we have new friends: ng-controller, ng-repeat and $scope.

  • <strong>ng-controller</strong> is a directive that tells angular what function controller to use for a particular view. Every time AngularJS loads, it reads the ng-controller argument (in this case “TodoController”). Then, it will look for a function in plain old javascript object (POJO) with the same name or for angular.controller matching name.
  • <strong>$scope</strong> As mentioned earlier $scope‘s are the glue between the data models in the controllers and the views. Take a look to our “TodoController” it has a parameter named $scope. AngularJS is going to pass ($inject) that parameter, and whatever you attach to it, it will be available in the view. In this example is the particular is the todos array of objects.
  • <strong>ng-repeat</strong> as its name implies, it is going to “repeat” the element and sub-elements where this directive is declared. It is going to iterate for each element in the $scope.todos array.
  • <strong>ng-model</strong> notice that the checkbox is bound to the todo.completed. If todo.completedis true, then the checkbox is going to be checked and vice versa.
2.5 AngularJS Modules

Modules are a way to encapsulate different parts of your application. They allow reusing code in other places. Here’s an example of how to rewrite our controller using modules.

AngularJS Module Example

angular.module('app', [])
  .controller('TodoController', ['$scope', function ($scope) {
    $scope.todos = [
      { title: 'Learn Javascript', completed: true },
      { title: 'Learn Angular.js', completed: false },
      { title: 'Love this tutorial', completed: true },
      { title: 'Learn Javascript design patterns', completed: false },
      { title: 'Build Node.js backend', completed: false },
    ];
  }]);

Notice the <html ng-app="app"> in the example below

Using modules brings many advantages. They can be loaded in any order, and parallel dependency loading. Also, tests can only load the required modules and keep it fast, clear view of the dependencies.

2.6 AngularJS Templates

Templates contain HTML and Angular elements (directives, markup, filters or form controls). They can be cached and referenced by an id.

Here’s an example:

AngularJS Template Example

<script type="text/ng-template" id="/todos.html">
  <ul>
    <li ng-repeat="todo in todos">
      <input type="checkbox" ng-model="todo.completed">
      {{ todo.name }}
    </li>
  </ul>
</script>

Does the code inside looks familiar? ;)

Notice they are inside the script and has a type of text/ng-template.

AngularJS Routes (ngRoutes)

ngRoutes module allows changing what we see in the app depending on the URL (route). It, usually, uses templates to inject the HTML into the app.

It does not come with AngularJS core module, so we have to list it as a dependency. We are going to get it from Google CDN:

<script src="<a href="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.min.js"></script>" target="_blank">https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.min.js"></script></a>

NEW FEATURE: add notes to the todo tasks. Let’s start with the routes!

angular.module('app', ['ngRoute'])
  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: '/todos.html',
        controller: 'TodoController'
      });
  }]);

  • First notice that we removed ng-controller="TodoController" from the body tag. The controllers are now called based on the route.
  • ngView is a directive used by $routeProvider to render HTML into it. Every time the URL changes, it will inject a new HTML template and controller into ngView.
2.8 AngularJS Services (Factories)

Notice that if you want to create a 2nd controller and share $scope.todos it is not possible right now. That is when services become handy. Services are a way to inject data dependencies into controllers. They are created through factories. Let’s see it in action:

AngularJS Service Factory Example

 angular.module('app', ['ngRoute'])

  .factory('Todos', function(){
    return [
      { name: 'AngularJS Directives', completed: true },
      { name: 'Data binding', completed: true },
      { name: '$scope', completed: true },
      { name: 'Controllers and Modules', completed: true },
      { name: 'Templates and routes', completed: true },
      { name: 'Filters and Services', completed: false },
      { name: 'Get started with Node/ExpressJS', completed: false },
      { name: 'Setup MongoDB database', completed: false },
      { name: 'Be awesome!', completed: false },
    ];
  })

  .controller('TodoController', ['$scope', 'Todos', function ($scope, Todos) {
    $scope.todos = Todos;
  }])

We are now injecting the data dependency Todo into the controllers. This way we could reuse the data to any controller or module that we need to. This is not only used for static data like the array. But we could also do server calls using $http or even RESTful $resource.

Let’s say we want to show the details of the task when we click on it. For that, we need to create a 2nd controller, template, and route that uses this service:

(NOTE: Click on the links and it will take you to the todo details. Use backspace key to go back to the main menu)

This is what is happening:

  1. In the HTML tab, we created a second template /todoDetails.html which contains the todo details we want to show.
  2. Also, in our previous template /todos.html we want to have a link that points to the tododetail. We are using the $index which is the corresponding order number in a ng-repeat.
  3. In the JS tab, we created a new $routeProvider . It points to a new controller TodoDetailCtrl and the template that we created on #1. The :id parameter it is accessible in the controllers through $routeParams.
  4. We Created the new controller TodoDetailCtrl. Also, we injected the dependencies which are $scope, Todos (factory), and $routeParams which will have the id param.
  5. Set the $scope in the new controller. Instead of using the whole array, we are going to select only the one that we need using the id that we set in step #2.

NOTE: in codepen, you will not see the URL. If you want to see it changing, you can download the whole example an open it from here.

2.9 AngularJS Filters

Filters allow you to format and transform data. They change the output of expressions inside the curly braces. AngularJS comes with a bunch of useful filters.

Built-in Filters:

  • filter: search for a given string in an array and return matches.
  • Number: adds comma-separated 1000’s and two decimal places.
  • Currency: the same as Number and adds a $ in front.
  • Date: takes a Unix timestamp (e.g. 1288323623006) or date string and output it in the format that you specify (e.g. ‘longDate’ or fragments ‘yyyy’ for a four-digit year). For a full list see here.
  • JSON: converts javascript objects to JSON strings.
  • lowercase/uppercase: converts strings to lowercase/uppercase.
  • limitTo: number of elements from an array to show.
  • orderBy: order array of objects by a key that you specify.

Note you can also chain many filters and also define your own filters.

NEW FEATURE: Search todo tasks by name. Let’s use a filter to solve that problem.

<script type="text/ng-template" id="/todos.html">
  Search: <input type="text" ng-model="search.name">
  <ul>
    <li ng-repeat="todo in todos | filter: search">
      <input type="checkbox" ng-model="todo.completed">
      <a href="#/{{$index}}">{{todo.name}}</a>
    </li>
  </ul>
</script>

Notice that we are using search.name in the ng-model for search. That will limit the search to the name attribute and search.notes will look inside the notes only. Guest what search would do then? Precisely! It searches in all the attributes. Fork the following example and try it out:

3.What’s next?

Congrats! You have completed part 1 of this 3 part series. We are going to build upon the things learned in here, in the next post we are going to setup a backend in NodeJS and MongoDB and connect it to AngularJS to provide a full featured CRUD app. Continue with:

I also have created BackboneJS tutorials check it out:

ng-test

Congrats, you have reached this far! It is time to test what you have learned. Test-Driven Learning (TDL) ;). Here’s the challenge: open this file on your favorite code editor. Copy the boilerplate code and built the full app that we just build in the previous examples. Of course, you can take a peek from time to time if you get stuck ;)

Download this file as…:

index.html

-OR-

Fork and edit online:

ng-solution

This is the full solution and you can see it live in here.

<html ng-app="app">
	<head>
	  <title>ngTodo</title>
	</head>
	<body>
	

	<ng-view></ng-view>
	

	<!-- Libraries -->
	<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
	<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.min.js"></script>
	

	<!-- Template -->
	<script type="text/ng-template" id="/todos.html">
	  Search: <input type="text" ng-model="search.name">
	  <ul>
	    <li ng-repeat="todo in todos | filter: search">
	      <input type="checkbox" ng-model="todo.completed">
	      <a href="#/{{$index}}">{{todo.name}}</a>
	    </li>
	  </ul>
	</script>
	

	<script type="text/ng-template" id="/todoDetails.html">
	  <h1>{{ todo.name }}</h1>
	  completed: <input type="checkbox" ng-model="todo.completed">
	  note: <textarea>{{ todo.note }}</textarea>
	</script>
	

	<script>
	  angular.module('app', ['ngRoute'])
	
	    //---------------
	    // Services
	    //---------------
	
	    .factory('Todos', function(){
	      return [
	        { name: 'AngularJS Directives', completed: true, note: 'add notes...' },
	        { name: 'Data binding', completed: true, note: 'add notes...' },
	        { name: '$scope', completed: true, note: 'add notes...' },
	        { name: 'Controllers and Modules', completed: true, note: 'add notes...' },
	        { name: 'Templates and routes', completed: true, note: 'add notes...' },
	        { name: 'Filters and Services', completed: false, note: 'add notes...' },
	        { name: 'Get started with Node/ExpressJS', completed: false, note: 'add notes...' },
	        { name: 'Setup MongoDB database', completed: false, note: 'add notes...' },
	        { name: 'Be awesome!', completed: false, note: 'add notes...' },
	      ];
	    })
	
	    //---------------
	    // Controllers
	    //---------------
	
	    .controller('TodoController', ['$scope', 'Todos', function ($scope, Todos) {
	      $scope.todos = Todos;
	    }])
	
	    .controller('TodoDetailCtrl', ['$scope', '$routeParams', 'Todos', function ($scope, $routeParams, Todos) {
	      $scope.todo = Todos[$routeParams.id];
	    }])
	
	    //---------------
	    // Routes
	    //---------------
	
	    .config(['$routeProvider', function ($routeProvider) {
	      $routeProvider
	        .when('/', {
	          templateUrl: '/todos.html',
	          controller: 'TodoController'
	        })
	
	        .when('/:id', {
	          templateUrl: '/todoDetails.html',
	          controller: 'TodoDetailCtrl'
	       });
	    }]);
	</script>
	

	</body>
	</html>

MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App

MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App

The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application

The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application

MEVN Tutorial: The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application. Previously we have a tutorial on build chat application using MEAN Stack, now we build this chat application using MEVN (MongoDB, Express.js, Vue.js 2, Node.js) Stack. The different just now we use Vue.js 2 and Axios, we keep using MongoDB, Node.js, Express, and Socket.io.

Table of Contents:
  • Create a New Vue.js 2 Application using Vue-CLI
  • Install Express.js as RESTful API Server
  • Install and Configure Mongoose.js
  • Create REST API for Accessing Chat Data
  • Create Mongoose.js Model for Room and Chat
  • Create Vue.js 2 Component and Routing
  • Add Module for RESTful API Access and Styling UI
  • Modify Vue.js 2 Component for Room List
  • Modify Vue.js 2 Component for Add Room
  • Modify Vue.js 2 Component for Join Room
  • Modify Vue.js 2 Component for Chat Room
  • Integrate Socket.io With Existing Non-Real-time Chat Application
  • Run and Test The Chat Application

The scenario is very simple, just the rooms and the chats for each room. The first page will show the list of the rooms. After the user enters the room and fills the username or nickname then the user enters the chats with other users.

.The following tools, frameworks, and modules are required for this tutorial:

  1. Node.js (use recommended version)
  2. Express.js
  3. MongoDB
  4. Mongoose.js
  5. Vue.js
  6. Vue-CLI
  7. Socket IO
  8. Axios
  9. Terminal (Mac/Linux) or Node.js Command Line (Windows)
  10. IDE or Text Editor (we use Atom)

We assume that you have already installed Node.js and able to run Node.js command line (Windows) or npm on the terminal (MAC/Linux). Open the terminal or Node command line then type this command to install vue-cli.

sudo npm install -g vue-cli

That where we start the tutorial. We will create the MEVN stack Chat application using vue-cli.

1. Create a New Vue.js 2 Application using Vue-CLI

To create a new Vue.js 2 application using vue-cli simply type this command from terminal or Node command line.

vue init webpack mevn-chat

There will be a lot of questions, just leave it as default by always pressing enter key. Next, go to the newly created Vue.js project folder then install all default required modules by type this command.

cd ./mevn-chat

Now, check the Vue.js 2 application by running the application using this command.

npm run dev

Open your browser then go to localhost:8080 and you should see this page when everything still on the track.

2. Install Express.js as RESTful API Server

Close the running Vue.js 2 app first by press ctrl+c then type this command for adding Express.js modules and its dependencies.

npm install --save express body-parser morgan body-parser serve-favicon

Next, create a new folder called bin then add a file called www on the root of the Vue.js project folder.

mkdir bin
touch bin/www

Open and edit www file then add these lines of codes that contains configuration for an HTTP server, PORT, and error handling.

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('mean-app:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

Next, change the default server what run by npm command. Open and edit package.json then replace startvalue inside scripts.

"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run build && node ./bin/www",
  "unit": "jest --config test/unit/jest.conf.js --coverage",
  "e2e": "node test/e2e/runner.js",
  "test": "npm run unit && npm run e2e",
  "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
  "build": "node build/build.js"
},

Next, create app.js in the root of project folder.

touch app.js

Open and edit app.js then add this lines of codes.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var bodyParser = require('body-parser');

var room = require('./routes/room');
var chat = require('./routes/chat');
var app = express();

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({'extended':'false'}));
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/rooms', express.static(path.join(__dirname, 'dist')));
app.use('/api/room', room);
app.use('/api/chat', chat);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

Next, create routes folder then create routes file for the room and chat.

mkdir routes
touch routes/room.js
touch routes/chat.js

Open and edit routes/room.js file then add this lines of codes.

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.send('Express RESTful API');
});

module.exports = router;

Do the same way with routes/chat.js. Now, run the server using this command.

npm start

You will see the previous Vue.js landing page when you point your browser to [http://localhost:3000](http://localhost:3000 "http://localhost:3000"). When you change the address to [http://localhost:3000/api/room](http://localhost:3000/api/room "http://localhost:3000/api/room") or [http://localhost:3000/api/chat](http://localhost:3000/api/chat "http://localhost:3000/api/chat") you will see this page.

3. Install and Configure Mongoose.js

We need to access data from MongoDB. For that, we will install and configure Mongoose.js. On the terminal type this command after stopping the running Express server.

npm install --save mongoose bluebird

Open and edit app.js then add this lines after another variable line.

var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
mongoose.connect('mongodb://localhost/mevn-chat', { promiseLibrary: require('bluebird') })
  .then(() =>  console.log('connection succesful'))
  .catch((err) => console.error(err));

Now, run MongoDB server on different terminal tab or command line or run from the service.

mongod

Next, you can test the connection to MongoDB run again the Node application and you will see this message on the terminal.

connection succesful

If you are still using built-in Mongoose Promise library, you will get this deprecated warning on the terminal.

(node:42758) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

That’s the reason why we added bluebird modules and register it as Mongoose Promise library.

4. Create Mongoose.js Model for Room and Chat

Add a models folder on the root of project folder for hold Mongoose.js model files then add Javascript file for Room and Chat.

mkdir models
touch models/Room.js
touch models/Chat.js

Next, open and edit models/Room.js then add this lines of codes.

var mongoose = require('mongoose'), Schema = mongoose.Schema;

var RoomSchema = new mongoose.Schema({
  room_name: String,
  created_date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Room', RoomSchema);

Open and edit models/Chat.js then add this lines of codes.

var mongoose = require('mongoose'), Schema = mongoose.Schema;

var ChatSchema = new mongoose.Schema({
  room : { type: Schema.Types.ObjectId, ref: 'Room' },
  nickname: String,
  message: String,
  created_date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Chat', ChatSchema);
5. Create Vue.js Component and Routing

Open and edit again routes/room.js then replace all codes with this.

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Room = require('../models/Room.js');

/* GET ALL ROOMS */
router.get('/', function(req, res, next) {
  Room.find(function (err, products) {
    if (err) return next(err);
    res.json(products);
  });
});

/* GET SINGLE ROOM BY ID */
router.get('/:id', function(req, res, next) {
  Room.findById(req.params.id, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* SAVE ROOM */
router.post('/', function(req, res, next) {
  Room.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* UPDATE ROOM */
router.put('/:id', function(req, res, next) {
  Room.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* DELETE ROOM */
router.delete('/:id', function(req, res, next) {
  Room.findByIdAndRemove(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

Open and edit again routes/chat.js then replace all codes with this.

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Chat = require('../models/Chat.js');

/* GET ALL CHATS */
router.get('/', function(req, res, next) {
  Chat.find(function (err, products) {
    if (err) return next(err);
    res.json(products);
  });
});

/* GET SINGLE CHAT BY ID */
router.get('/:id', function(req, res, next) {
  Chat.findById(req.params.id, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* SAVE CHAT */
router.post('/', function(req, res, next) {
  Chat.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* UPDATE CHAT */
router.put('/:id', function(req, res, next) {
  Chat.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* DELETE CHAT */
router.delete('/:id', function(req, res, next) {
  Chat.findByIdAndRemove(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

Run again the Express server then open the other terminal or command line to test the Restful API by type this command.

curl -i -H "Accept: application/json" localhost:3000/api/room

If that command return response like below then REST API is ready to go.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w"
Date: Sun, 05 Aug 2018 13:11:30 GMT
Connection: keep-alive

[]

Now, let’s populate Room collection with initial data that sent from RESTful API. Run this command to populate it.

curl -i -X POST -H "Content-Type: application/json" -d '{ "room_name":"Javascript" }' localhost:3000/api/room

You will see this response to the terminal if success.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 109
ETag: W/"6d-OGpcih/JWvJGrYAhMP+KBYQOvNQ"
Date: Sun, 05 Aug 2018 13:35:50 GMT
Connection: keep-alive

{"_id":"5b66fd3581b9291558dc90b7","room_name":"Javascript","created_date":"2018-08-05T13:35:49.803Z","__v":0}

6. Create Vue.js 2 Component and Routing

Now, it’s time for Vue.js 2 or front end part. First, create or add the component of the room list, add a room, join a room, chat room. Create all of those files into the components folder.

touch src/components/RoomList.vue
touch src/components/AddRoom.vue
touch src/components/JoinRoom.vue
touch src/components/ChatRoom.vue

Now, open and edit src/router/index.js then add the import for all above new components.

import Vue from 'vue'
import Router from 'vue-router'
import RoomList from '@/components/RoomList'
import AddRoom from '@/components/AddRoom'
import JoinRoom from '@/components/JoinRoom'
import ChatRoom from '@/components/ChatRoom'

Add the router to each component or page.

export default new Router({
  routes: [
    {
      path: '/',
      name: 'RoomList',
      component: RoomList
    },
    {
      path: '/add-room',
      name: 'AddRoom',
      component: AddRoom
    },
    {
      path: '/join-room/:id',
      name: 'JoinRoom',
      component: JoinRoom
    },
    {
      path: '/chat-room/:id/:nickname',
      name: 'ChatRoom',
      component: ChatRoom
    }
  ]
})

7. Add Axios and Bootstrap-Vue

For UI or styling, we are using Bootstrap Vue. BootstrapVue use to build responsive, mobile-first projects on the web using Vue.js and the world's most popular front-end CSS library Bootstrap v4. To install Bootstrap-Vue type this command on the terminal.

npm i bootstrap-vue [email protected]

Open and edit src/main.js then add the imports for Bootstrap-Vue.

import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import App from './App'
import router from './router'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Add this line after Vue.config.

Vue.use(BootstrapVue)

Next, we are using Axio for accessing REST API provided by Express.js. Axios is a promise-based HTTP client for the browser and node.js. To install it, in the terminal type this command.

npm install axios --save

8. Modify Vue.js 2 Component for Room List

Now, open and edit src/components/RoomList.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Room List
        <b-link href="#/add-room">(Add Room)</b-link>
      </h2>
      <b-table striped hover :items="rooms" :fields="fields">
        <template slot="actions" scope="row">
          <b-btn size="sm" @click.stop="join(row._id)">Join</b-btn>
        </template>
      </b-table>
      <ul v-if="errors && errors.length">
        <li v-for="error of errors">
          {{error.message}}
        </li>
      </ul>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'BookList',
  data () {
    return {
      fields: {
        room_name: { label: 'Room Name', sortable: true, 'class': 'text-center' },
        created_date: { label: 'Created Date', sortable: true },
        actions: { label: 'Action', 'class': 'text-center' }
      },
      rooms: [],
      errors: []
    }
  },
  created () {
    axios.get(`http://localhost:3000/api/room`)
    .then(response => {
      this.rooms = response.data
    })
    .catch(e => {
      this.errors.push(e)
    })
  },
  methods: {
    join (id) {
      this.$router.push({
        name: 'JoinRoom',
        params: { id: id }
      })
    }
  }
}
</script>

There are template and script in one file. The template block contains HTML tags. Script block contains variables, page lifecycle and methods or functions.

9. Modify Vue.js 2 Component for Add Room

Now, open and edit src/components/AddRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col align-self="start">&nbsp;</b-col>
    <b-col cols="6" align-self="center">
      <h2>
        Add Room
        <b-link href="#/">(Room List)</b-link>
      </h2>
      <b-form @submit="onSubmit">
        <b-form-group id="fieldsetHorizontal"
                  horizontal
                  :label-cols="4"
                  breakpoint="md"
                  label="Enter Room Name">
          <b-form-input id="room_name" :state="state" v-model.trim="room.room_name"></b-form-input>
        </b-form-group>
        <b-button type="submit" variant="primary">Save</b-button>
      </b-form>
    </b-col>
    <b-col align-self="end">&nbsp;</b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'AddRoom',
  data () {
    return {
      room: {}
    }
  },
  methods: {
    onSubmit (evt) {
      evt.preventDefault()
      axios.post(`http://localhost:3000/api/room`, this.room)
      .then(response => {
        this.$router.push({
          name: 'RoomList'
        })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

That code contains the template for room form, the script that contains Vue.js 2 codes for hold room model and methods for saving room to RESTful API.

10. Modify Vue.js 2 Component for Join Room

Now, open and edit src/components/JoinRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="6">
      <h2>
        Join Room
        <b-link href="#/">(Room List)</b-link>
      </h2>
      <b-form @submit="onSubmit">
        <b-form-group id="fieldsetHorizontal"
                  horizontal
                  :label-cols="4"
                  breakpoint="md"
                  label="Enter Nickname">
          <b-form-input id="nickname" :state="state" v-model.trim="chat.nickname"></b-form-input>
        </b-form-group>
        <b-button type="submit" variant="primary">Join</b-button>
      </b-form>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'JoinRoom',
  data () {
    return {
      chat: {}
    }
  },
  methods: {
    onSubmit (evt) {
      evt.preventDefault()
      this.chat.room = this.$route.params.id
      this.chat.message = this.chat.nickname + ' join the room'
      axios.post(`http://localhost:3000/api/chat`, this.chat)
      .then(response => {
        this.$router.push({
          name: 'ChatRoom',
          params: { id: this.$route.params.id, nickname: response.data.nickname }
        })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

That code contains the template for join room form, the script that contains Vue.js 2 codes for hold chat model and methods for saving room to RESTful API.

11. Modify Vue.js 2 Component for Chat Room

Now, open and edit src/components/JoinRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Chat Room
      </h2>
      <b-list-group class="panel-body">
        <b-list-group-item v-for="(item, index) in chats" class="chat">
          <div class="left clearfix" v-if="item.nickname === nickname">
            <b-img left src="http://placehold.it/50/55C1E7/fff&text=ME" rounded="circle" width="75" height="75" alt="img" class="m-1" />
            <div class="chat-body clearfix">
              <div class="header">
                <strong class="primary-font">{{ item.nickname }}</strong> <small class="pull-right text-muted">
                <span class="glyphicon glyphicon-time"></span>{{ item.created_date }}</small>
              </div>
              <p>{{ item.message }}</p>
            </div>
          </div>
          <div class="right clearfix" v-else>
            <b-img right src="http://placehold.it/50/55C1E7/fff&text=U" rounded="circle" width="75" height="75" alt="img" class="m-1" />
            <div class="chat-body clearfix">
              <div class="header">
                <strong class="primary-font">{{ item.nickname }}</strong> <small class="pull-right text-muted">
                <span class="glyphicon glyphicon-time"></span>{{ item.created_date }}</small>
              </div>
              <p>{{ item.message }}</p>
            </div>
          </div>
        </b-list-group-item>
      </b-list-group>
      <ul v-if="errors && errors.length">
        <li v-for="error of errors">
          {{error.message}}
        </li>
      </ul>
      <b-form @submit="onSubmit" class="chat-form">
        <b-input-group prepend="Message">
          <b-form-input id="message" :state="state" v-model.trim="chat.message"></b-form-input>
          <b-input-group-append>
            <b-btn type="submit" variant="info">Send</b-btn>
          </b-input-group-append>
        </b-input-group>
      </b-form>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'ChatRoom',
  data () {
    return {
      chats: [],
      errors: [],
      nickname: this.$route.params.nickname,
      chat: {}
    }
  },
  created () {
    axios.get(`http://localhost:3000/api/chat/` + this.$route.params.id)
    .then(response => {
      this.chats = response.data
    })
    .catch(e => {
      this.errors.push(e)
    })
  },
  methods: {
    logout (id) {
      this.$router.push({
        name: 'JoinRoom',
        params: { id: id }
      })
    },
    onSubmit (evt) {
      evt.preventDefault()
      this.chat.room = this.$route.params.id
      this.chat.nickname = this.$route.params.nickname
      axios.post(`http://localhost:3000/api/chat`, this.chat)
      .then(response => {
        // this.$router.push({
        //   name: 'ChatRoom',
        //   params: { id: this.$route.params.id, nickname: response.data.nickname }
        // })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

<style>
  .chat .left .chat-body {
    text-align: left;
    margin-left: 100px;
  }

  .chat .right .chat-body {
    text-align: right;
    margin-right: 100px;
  }

  .chat .chat-body p {
    margin: 0;
    color: #777777;
  }

  .panel-body {
    overflow-y: scroll;
    height: 350px;
  }

  .chat-form {
    margin: 20px auto;
    width: 80%;
  }
</style>

That code contains the template of the main chat application consist of chat list and sends message form.

12. Integrate Socket.io With Existing Non-Real-time Chat Application

Previous steps show you a regular and non-realtime transaction chat application. Now, we will make it real-time by using Socket.io. First, install socket.io module by type this command.

npm install --save socketio socket.io-client

Next, open and edit routes/chat.js then declare the Socket IO and http module.

var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);

Add this lines of codes for Socket IO functions.

server.listen(4000);

// socket io
io.on('connection', function (socket) {
  console.log('User connected');
  socket.on('disconnect', function() {
    console.log('User disconnected');
  });
  socket.on('save-message', function (data) {
    console.log(data);
    io.emit('new-message', { message: data });
  });
});

In that code, we are running Socket.io to listen for ‘save-message’ that emitted from the client and emit ‘new-message’ to the clients. Next, open and edit src/components/JoinRoom.vue then add this import.

import * as io from 'socket.io-client'

Declare Socket IO variable.

data () {
  return {
    chat: {},
    socket: io('http://localhost:4000')
  }
},

Add Socket IO emit function after successful join room.

axios.post(`http://localhost:3000/api/chat`, this.chat)
.then(response => {
  this.socket.emit('save-message', { room: this.chat.room, nickname: this.chat.nickname, message: 'Join this room', created_date: new Date() });
  this.$router.push({
    name: 'ChatRoom',
    params: { id: this.$route.params.id, nickname: response.data.nickname }
  })
})
.catch(e => {
  this.errors.push(e)
})

Next, open and edit src/components/ChatRoom.vue then add this imports and use as Vue module.

import Vue from 'vue'
import * as io from 'socket.io-client'
import VueChatScroll from 'vue-chat-scroll'
Vue.use(VueChatScroll)

Declare Socket IO variable.

data () {
  return {
    chats: [],
    errors: [],
    nickname: this.$route.params.nickname,
    chat: {},
    socket: io('http://localhost:4000')
  }
},

Add this Socket IO on function to created method.

created () {
  axios.get(`http://localhost:3000/api/chat/` + this.$route.params.id)
  .then(response => {
    this.chats = response.data
  })
  .catch(e => {
    this.errors.push(e)
  })

  this.socket.on('new-message', function (data) {
    if(data.message.room === this.$route.params.id) {
      this.chats.push(data.message)
    }
  }.bind(this))
},

Add Logout function inside methods and add Socket IO emit method in the POST response.

methods: {
  logout () {
    this.socket.emit('save-message', { room: this.chat.room, nickname: this.chat.nickname, message: this.chat.nickname + ' left this room', created_date: new Date() });
    this.$router.push({
      name: 'RoomList'
    })
  },
  onSubmit (evt) {
    evt.preventDefault()
    this.chat.room = this.$route.params.id
    this.chat.nickname = this.$route.params.nickname
    axios.post(`http://localhost:3000/api/chat`, this.chat)
    .then(response => {
      this.socket.emit('save-message', response.data)
      this.chat.message = ''
    })
    .catch(e => {
      this.errors.push(e)
    })
  }
}

Finally, to make Chat list always scroll to the bottom of Chat element add install this module.

npm install --save vue-chat-scroll

That module already imported and declared above. Next, add to the Chat element.

<b-list-group class="panel-body" v-chat-scroll>
  ...
</b-list-group>

13. Run and Test The MEVN (Vue.js 2) Chat Application**

To run this MEVN (Vue.js 2) Chat Application locally, make sure MongoDB service is running. Type this command to build the Vue.js 2 application then run the Express.js application.

npm start

Next, open the different browser (ex: Chrome and Firefox) then go to the localhost:3000 on both of the browsers. You will see this page and you can start Chat.

That it’s, the MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App. You can find the full working source code on our GitHub.

Thanks!

=============================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ MERN Stack Front To Back: Full Stack React, Redux & Node.js

☞ The Complete Node.js Developer Course (3rd Edition)

☞ Angular & NodeJS - The MEAN Stack Guide

☞ NodeJS - The Complete Guide (incl. MVC, REST APIs, GraphQL)

☞ Docker for Node.js Projects From a Docker Captain

☞ Intro To MySQL With Node.js - Learn To Use MySQL with Node!

☞ Node.js Absolute Beginners Guide - Learn Node From Scratch

☞ React Node FullStack - Social Network from Scratch to Deploy

☞ Selenium WebDriver - JavaScript nodeJS webdriver IO & more!

☞ Complete Next.js with React & Node - Beautiful Portfolio App

☞ Build a Blockchain & Cryptocurrency | Full-Stack Edition

Angular 7 CRUD With Node.JS API

Angular 7 CRUD With Node.JS API

In this article, we are going to create a very basic Angular 7 CRUD (Create, Read, Update and Delete) application with Node.JS REST API endpoints. With the help of our REST API, we will work on our MongoDB database, and perform various operations on Products collection.

In this article, we are going to create a very basic Angular 7 CRUD (Create, Read, Update and Delete) application with Node.JS REST API endpoints. With the help of our REST API, we will work on our MongoDB database, and perform various operations on Products collection.

Angular 7 Prerequisites

Before start writing the Angular 7 app, please make sure that, you have latest version of:

  • Node.JS and NPM installed on your system
  • Angular 7 CLI installed on your system
MongoDB Backend Overview

Let me introduce you about the MongoDB Document structure. We have Products collection in our database, and this Products collection have multiple documents related to various products.

This is the screenshot of our MongoDB Products collection.

And this is the screenshot of each Product document.

{
    "_id": {
        "$oid": "5c3162ed7829481868c3afe9"
    },
    "title": "HP",
    "description": "15 inch HP Laptop",
    "price": 1500,
    "company": "HP",
    "createdAt": {
        "$date": "2019-01-06T02:07:41.477Z"
    },
    "updatedAt": {
        "$date": "2019-01-06T02:07:41.477Z"
    },
    "__v": 0
}

Node.JS Rest API Endpoints

You can get all details of Node.JS Rest API in the above link, which I shared on the top of this post. Let me share again, which endpoints we have for our application. These are the list of our API endpoints.

// Create a new Product
    app.post('/products', products.create);

    // Retrieve all Products
    app.get('/products', products.findAll);

    // Retrieve a single Product with productId
    app.get('/products/:productId', products.findOne);

    // Update a Note with productId
    app.put('/products/:productId', products.update);

    // Delete a Note with productId
    app.delete('/products/:productId', products.delete);

Creating Angular 7 Project

Let’s start with our Angular 7 app. Using above Node.JS API endpoints, we are going to perform CRUD operation. This will be the screen of our Angular app.

Create your angular app using the ng new  command in your terminal.

1. Creating Model Class In Angular

After creating our app, let’s first create our Product Model class as per our database Product Document properties.

ProductModel.ts

export class ProductModel {
    _id: string;
    title: String;
    description: String;
    price: Number;
    company: String;
}

2. Creating Service In Angular

Let’s create our Product Service class, which will interact with our Node.JS endpoints for all product related operations.

product.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ProductModel } from './ProductModel';

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  constructor(private http: HttpClient) { }

  baseurl: string = "http://localhost:3000/";

  getAllProducts(){
    return this.http.get<ProductModel[]>(this.baseurl + 'Products');
  }

  getProductById(id: string){
    return this.http.get<ProductModel>(this.baseurl + 'Products' + '/' + id);
  }

  addProduct(product: ProductModel){
    return this.http.post(this.baseurl + 'Products', product);
  }

  deleteProduct(id: string){
    return this.http.delete(this.baseurl + 'Products' + '/' + id);
  }

  updateProduct(product: ProductModel){
    return this.http.put(this.baseurl + 'Products' + '/' + product._id, product);
  }
}

We have imported HttpClient and ProductModel in our service class.

This is our service url endpoint.

baseurl: string = “http://localhost:3000/”;
In this class, we have different methods. These are:

  • Node.JS and NPM installed on your system
  • Angular 7 CLI installed on your system

Import Product Service in app.module

Now, after creating the product service, we have to import this in our app.module.ts file. Let’s do it now.

app.module.ts

Add this line on the top of app.module.ts file.

baseurl: string = “http://localhost:3000/”;
And this ProductService in Providers[] array.
baseurl: string = “http://localhost:3000/”;## 3. Creating Component For Listing All Products

First of all, we are going to fetch all the products in our database. Create list-products component, and add this to our routing module. This component will fetch all the products from the database and display it in tabular form. Add routing details of this component in our route file.

app-routing.module.ts

Import ListProductsComponent in our routing file.

baseurl: string = “http://localhost:3000/”;
And, add this in routes constant.
baseurl: string = “http://localhost:3000/”;
This is our list-products component html file.

list-products.component.html

<div class="container col-md-12"> 
    <div>
        <button (click)="addProduct()" class="btn btn-info">Add Product</button>
      </div> 
  <h2>All Products</h2>  
  <div class="table-responsive table-container">  
    <table class="table table-dark table-hover table-striped">  
      <thead class="thead-light">  
        <tr>  
          <th>Title</th>  
          <th>Description</th> 
          <th>Price($)</th> 
          <th>Company</th>
          <th>Action</th> 
        </tr>  
      </thead>  
      <tbody>  
        <tr *ngFor="let product of products"> 
           
          <td>{{product.title}}</td>  
          <td>{{product.description}}</td>
          <td>{{product.price | currency : "USD" : true}}</td> 
          <td>{{product.company}}</td> 
           
          <td>  
            <button (click)="deleteProduct(product)" class="btn btn-info"> Delete</button>  
            <button (click)="updateProduct(product)" style="margin-left: 20px;" class="btn btn-info"> Edit</button>  
          </td>  
        </tr>  
      </tbody>  
    </table>  
  </div> 
   
</div> 

list-products.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from "@angular/router";
import { ProductModel } from '../ProductModel';
import { ProductService } from '../product.service';

@Component({
  selector: 'app-list-products',
  templateUrl: './list-products.component.html',
  styleUrls: ['./list-products.component.css']
})
export class ListProductsComponent implements OnInit {

  products: ProductModel[];

  constructor(private productService: ProductService, private router: Router) { }

  ngOnInit() {
    this.getAllProducts();
  }

  getAllProducts(): void {
    this.productService.getAllProducts().subscribe(data=>{
      this.products = data;
    });
  };

  addProduct(): void {
    this.router.navigate(['add-product']);
  }

  deleteProduct(product: ProductModel){
    
    this.productService.deleteProduct(product._id).subscribe(data=>{
      console.log(data);
      this.getAllProducts();
    });
  }

  updateProduct(product: ProductModel){
    localStorage.removeItem("productId");
    localStorage.setItem("productId", product._id);
    this.router.navigate(['edit-product']);
  }

}

To get the list of all products, we have created getAllProducts() function, and call it in ngOnInit().

4. Creating Component For Adding Product

This component will be used to add a product in our database. Add routing details of this component in our route file.

app-routing.module.ts

Add this line on the top of app-routing.module.ts file.

baseurl: string = “http://localhost:3000/”;
And, add this in routes constant.
baseurl: string = “http://localhost:3000/”;
This is our add-product component file.

add-product.component.html

<div class="col-md-3">
    <h2 class="text-center">Add Product</h2>
    <form [formGroup]="addForm" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label>Title:</label>
        <input type="text" formControlName="title" placeholder="Title" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.title.errors }">
        <div *ngIf="submitted && f.title.errors">
          <div *ngIf="f.title.errors.required">Title is required</div>
        </div>
      </div>
  
      <div class="form-group">
        <label>Description:</label>
        <input type="text" formControlName="description" placeholder="Description" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.description.errors }">
        <div *ngIf="submitted && f.description.errors">
          <div *ngIf="f.description.errors.required">Description is required</div>
        </div>
      </div>
  
      <div class="form-group">
        <label>Price ($):</label>
        <input type="text" formControlName="price" placeholder="Price" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.price.errors }">
        <div *ngIf="submitted && f.price.errors">
          <div *ngIf="f.price.errors.required">Price is required</div>
        </div>
      </div>

      <div class="form-group">
          <label>Company:</label>
          <input type="text" formControlName="company" placeholder="Company" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.company.errors }">
          <div *ngIf="submitted && f.company.errors">
            <div *ngIf="f.company.errors.required">Company is required</div>
          </div>
      </div>
  
      <button type="submit"  class="btn btn-info">Save</button>
    </form>
  </div>

add-product.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, Validators } from "@angular/forms";
import { ProductService } from '../product.service';
import { first } from "rxjs/operators";
import { Router } from "@angular/router";

@Component({
  selector: 'app-add-product',
  templateUrl: './add-product.component.html',
  styleUrls: ['./add-product.component.css']
})
export class AddProductComponent implements OnInit {

  constructor(private formBuilder: FormBuilder, private router: Router, private productService: ProductService) { }

  addForm: FormGroup;
  submitted = false;

  ngOnInit() {
    this.addForm = this.formBuilder.group({
      _id: [],
      title: ['', Validators.required],
      description: ['', Validators.required],
      price: ['', Validators.required],
      company: ['', Validators.required]
    });
  }

  onSubmit(){
    this.submitted = true;
    
    if(this.addForm.valid){
      this.productService.addProduct(this.addForm.value)
      .subscribe( data => {
        console.log(data);
        this.router.navigate(['']);
      });
    }
  }

  // get the form short name to access the form fields
  get f() { return this.addForm.controls; }

}

In this component class, we have used Validators to check whether all required fields are filled or not. When user click on Add Product button on Product List page, user will be navigating to the Add Product page.

                   ![](https://i1.wp.com/www.zeptobook.com/wp-content/uploads/2019/01/Add-Product.png?fit=635%2C1024&ssl=1)

In the above pic, if you try to save without filing all the fields, it will red highlight the textbook and let you not to save the product. So, fill all the fields and save the new product. As soon as your product is saved, you will be redirecting again to the all products page with newly product added.

Also, notice the following code in list-products.component.html file. We have (click) event on the Add Product button, and calling the addProduct() method of list-products.component.ts class. In this method, we are simply navigating to our route add-product, and opening the add-product component page.

addProduct(): void {
    this.router.navigate(['add-product']);
  }

5. Delete Product

In order to delete any product, we don’t need to create any component. For each product in grid on list-products.component.html component, we have Delete button attached to it. There is an deleteProduct(product) method on (click) event of this Delete button. deleteProduct(product) method is defined in list-products.component.ts file.

deleteProduct(product: ProductModel){
    this.productService.deleteProduct(product._id).subscribe(data=>{
      console.log(data);
      this.getAllProducts();
    });
  }

After deleting the product, we are fetching all the products again by calling this.getAllProducts() method.

6. Creating Component For Updating Product

In order to update any product, we are going to create another component. Create component edit-product in your app. Add creating your component, we have to define the route as well in your app. Open the app-routing.module.tsfile and import this component there.

app-routing.module.ts

baseurl: string = “http://localhost:3000/”;
Add the route in routes constant.
baseurl: string = “http://localhost:3000/”;#### edit-product.component.html

<div class="col-md-3">
  <h2 class="text-center">Edit Product</h2>
  <form [formGroup]="editForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label>Title:</label>
      <input type="text" formControlName="title" placeholder="Title" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.title.errors }">
      <div *ngIf="submitted && f.title.errors">
        <div *ngIf="f.title.errors.required">Title is required</div>
      </div>
    </div>

    <div class="form-group">
      <label>Description:</label>
      <input type="text" formControlName="description" placeholder="Description" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.description.errors }">
      <div *ngIf="submitted && f.description.errors">
        <div *ngIf="f.description.errors.required">Description is required</div>
      </div>
    </div>

    <div class="form-group">
      <label>Price ($):</label>
      <input type="text" formControlName="price" placeholder="Price" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.price.errors }">
      <div *ngIf="submitted && f.price.errors">
        <div *ngIf="f.price.errors.required">Price is required</div>
      </div>
    </div>

    <div class="form-group">
        <label>Company:</label>
        <input type="text" formControlName="company" placeholder="Company" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.company.errors }">
        <div *ngIf="submitted && f.company.errors">
          <div *ngIf="f.company.errors.required">Company is required</div>
        </div>
    </div>

    <button type="submit"  class="btn btn-info">Update</button>
  </form>
</div>

Our edit-product component html file is similar to add-product component html file. So, you can simply copy paste the same html here.

edit-product.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, Validators } from "@angular/forms";
import { ProductService } from '../product.service';
import { first } from "rxjs/operators";
import { Router } from "@angular/router";
import { ProductModel } from '../ProductModel';

@Component({
  selector: 'app-edit-product',
  templateUrl: './edit-product.component.html',
  styleUrls: ['./edit-product.component.css']
})
export class EditProductComponent implements OnInit {

  product: ProductModel;
  editForm: FormGroup;
  submitted = false;

  constructor(private formBuilder: FormBuilder, private router: Router, private productService: ProductService) { }

  ngOnInit() {
    let productId = localStorage.getItem("productId");
    if(!productId){
      alert("Something wrong!");
      this.router.navigate(['']);
      return;
    }

    this.editForm = this.formBuilder.group({
      _id: [],
      title: ['', Validators.required],
      description: ['', Validators.required],
      price: ['', Validators.required],
      company: ['', Validators.required]
    });

    this.productService.getProductById(productId).subscribe(data=>{
      console.log(data);
      this.editForm.patchValue(data); //Don't use editForm.setValue() as it will throw console error
    });
  }

  // get the form short name to access the form fields
  get f() { return this.editForm.controls; }

  onSubmit(){
    this.submitted = true;
    
    if(this.editForm.valid){
      this.productService.updateProduct(this.editForm.value)
      .subscribe( data => {
        console.log(data);
        this.router.navigate(['']);
      });
    }
  }

}

Here, we are using localStorage.getItem() to get the productId, which was stored in our list-product.component.ts file using localStorage.setItem(). So, let’s first get the productId first from localStorage() in ngOnInit() method. See below code, how we are setting productId in localStorage(). After that, we are navigating to our edit component page.

list-product.component.ts

updateProduct(product: ProductModel){
    localStorage.removeItem("productId");
    localStorage.setItem("productId", product._id);
    this.router.navigate(['edit-product']);
  }

After retrieving the productId from localStorage, we are going to get the product using this productId in ngOnInit() function.

edit-product.component.ts

this.productService.getProductById(productId).subscribe(data=>{
      console.log(data);
      this.editForm.patchValue(data); //Don't use editForm.setValue() as it will throw console error
    });

On successful, we are setting the form values with the response data using patchValue() method.

                                                ![](https://i2.wp.com/www.zeptobook.com/wp-content/uploads/2019/01/Edit-Product.png?resize=387%2C504&ssl=1)

Update the values in the form and click on Update button. It will call the onSubmit() function of edit-product.component.ts class file. After updating the values in database, it will be redirected to product listing component page.

So, we have done with our CRUD operation on our product model. Let me show you complete app-routing and app.module files.

Angular 7 Routing: app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { ListProductsComponent } from './list-products/list-products.component';
import { AddProductComponent } from './add-product/add-product.component';
import { EditProductComponent } from './edit-product/edit-product.component';

const routes: Routes = [
  { path: 'add-product', component: AddProductComponent },
  { path: 'edit-product', component: EditProductComponent},
  { path: '', component: ListProductsComponent, pathMatch: 'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Angular 7 Module: app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { ReactiveFormsModule } from "@angular/forms"; 

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ProductService } from './product.service';
import { ListProductsComponent } from './list-products/list-products.component';
import { AddProductComponent } from './add-product/add-product.component';
import { EditProductComponent } from './edit-product/edit-product.component';

@NgModule({
  declarations: [
    AppComponent,
    ListProductsComponent,
    AddProductComponent,
    EditProductComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    ReactiveFormsModule
  ],
  providers: [ProductService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Install Bootstrap and FontAwesome in Angular 7

Use this npm command to install the Bootstrap and FontAwesome in your project.

baseurl: string = “http://localhost:3000/”;## Importing the Bootstrap and FontAwesome

After installation, you can add these css files in your styles.css file.

/* You can add global styles to this file, and also import other style files */
@import "~bootstrap/dist/css/bootstrap.css";
@import "~font-awesome/css/font-awesome.css";

Angular 7 Project Structure

This will be our app structure.

Download

You can download all the project files from GitHub.

Download Project Files

Summary

In this blog, we learned about how to perform CRUD operation on our MongoDB database using Node.Js Api. I also shared the project files on Github, from where you can view and download it.

By : Adesh

Angular 8 Node & Express JS File Upload

Angular 8 Node & Express JS File Upload

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

We are also going to take the help of Node.js to create the backend server for Image or File uploading demo. Initially, we’ll set up an Angular 8 web app from scratch using Angular CLI. You must have Node.js and Angular CLI installed in your system.

We’ll create the local server using Node.js and multer middleware. Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. Once we are done setting up front-end and backend for our File uploading demo then, we’ll understand step by step how to configure file uploading in Angular 8 app using Node server.

Prerequisite

In order to show you Angular 8 File upload demo, you must have Node.js and Angular CLI installed in your system. If not then check out this tutorial: Set up Node JS

Run following command to install Angular CLI:

npm install @angular/cli -g

Install Angular 8 App

Run command to install Angular 8 project:

ng new angular-node-file-upload

# ? Would you like to add Angular routing? No
# ? Which stylesheet format would you like to use? CSS
cd angular-node-file-upload

Show Alert Messages When File Uploaded

We are going to install and configure ngx-toastr an NPM package which helps in showing the alert message when the file is uploaded on the node server.

npm install ngx-toastr --save

The ngx-toastr NPM module requires @angular/animations dependency:

npm install @angular/animations --save

Then, add the ngx-toastr CSS in angular.json file:

"styles": [
    "src/styles.css",
    "node_modules/ngx-toastr/toastr.css"
]

Import BrowserAnimationsModule and ToastrModule in app.module.ts file:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
 
@NgModule({
  imports: [
    CommonModule,
    BrowserAnimationsModule, // required animations module
    ToastrModule.forRoot() // ToastrModule added
  ]
})

export class AppModule { }

Install & Configure ng-file-upload Directive

In this step, we’ll Install and configure ng-file-upload library in Angular 8 app. Run command to install ng-file-upload library.

npm install ng2-file-upload

Once the ng2-file-upload directive is installed, then import the FileSelectDirective and FormsModule in app.module.ts. We need FormsModule service so that we can create the file uploading component in Angular.

import { FileSelectDirective } from 'ng2-file-upload';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    FileSelectDirective
  ],
  imports: [
    FormsModule
  ]
})

export class AppModule { }

Setting Up Node Backend for File Upload Demo

To upload the file on the server, we need to set up a separate backend. In this tutorial, we will be using Node & Express js to create server locally along with multer, express js, body-parser, and dotenv libraries.

Run command to create backend folder in Angular app’s root directory:

mkdir backend && cd backend

In the next step, create a specific package.json file.

npm init

Run command to install required dependencies:

npm install express cors body-parser multer dotenv --save

In order to get rid from starting the server again and again, install nodemon NPM package. Use –-save-dev along with the npm command to register in the devDependencies array. It will make it available for development purpose only.

npm install nodemon --save-dev

Have a look at final pacakge.json file for file upload demo backend:

{
  "name": "angular-node-file-upload",
  "version": "1.0.0",
  "description": "Angualr 8 file upload demo app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Digamber Rawat",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.0.0",
    "express": "^4.17.1",
    "multer": "^1.4.1"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  }
}

Create a file by the name of server.js inside backend folder:

Configure Server.js

To configure our backend we need to create a server.js file. In this file we’ll keep our backend server’s settings.

touch server.js

Now, paste the following code in backend > server.js file:

const express = require('express'),
  path = require('path'),
  cors = require('cors'),
  multer = require('multer'),
  bodyParser = require('body-parser');

// File upload settings  
const PATH = './uploads';

let storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, PATH);
  },
  filename: (req, file, cb) => {
    cb(null, file.fieldname + '-' + Date.now())
  }
});

let upload = multer({
  storage: storage
});

// Express settings
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

app.get('/api', function (req, res) {
  res.end('File catcher');
});

// POST File
app.post('/api/upload', upload.single('image'), function (req, res) {
  if (!req.file) {
    console.log("No file is available!");
    return res.send({
      success: false
    });

  } else {
    console.log('File is available!');
    return res.send({
      success: true
    })
  }
});

// Create PORT
const PORT = process.env.PORT || 8080;
const server = app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Now, while staying in the backend folder run the below command to start the backend server:

nodemon server.js

If everything goes fine then you’ll get the following output:

[nodemon] 1.19.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
Connected to port 8080

Create Angular 8 File Upload Component

In this last step, we are going to create a file upload component in Angular 8 app using Express js API.

Get into the app.component.ts file and include the following code:

import { Component, OnInit } from '@angular/core';
import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
import { ToastrService } from 'ngx-toastr';

const URL = 'http://localhost:8080/api/upload';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  public uploader: FileUploader = new FileUploader({
    url: URL,
    itemAlias: 'image'
  });

  constructor(private toastr: ToastrService) { }

  ngOnInit() {
    this.uploader.onAfterAddingFile = (file) => {
      file.withCredentials = false;
    };
    this.uploader.onCompleteItem = (item: any, status: any) => {
      console.log('Uploaded File Details:', item);
      this.toastr.success('File successfully uploaded!');
    };
  }

}

Go to app.component.html file and add the given below code:

<div class="wrapper">
  <h2>Angular Image Upload Demo</h2>

  <div class="file-upload">
    <input type="file" name="image" ng2FileSelect [uploader]="uploader" accept="image/x-png,image/gif,image/jpeg" />
    <button type="button" (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
      Upload
    </button>
  </div>

</div>

Now, It’s time to start the Angular 8 app to check out the File upload demo in the browser. Run the following command:

ng serve --open

Make sure your NODE server must be running to manage the backend.

When you upload the image from front-end you’ll see your image files are saving inside the backend > uploads folder.

Conclusion

In this Angular 8 tutorial, we barely scratched the surface related to file uploading in a Node application. There are various other methods available on the internet through which you can achieve file uploading task quickly. However, this tutorial is suitable for beginners developers. I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others.