Go Offline – add to do list

Users of any data-driven mobile application should be able to read and write data without an Internet connection. When the app reconnects to the Internet, the details should be synced to the server. We will build an offline mobile app that stores data locally and syncs with an actual database when the connectivity is available.

What are we learning?

  • Use Pouch DB, a JavaScript DB, with Ionic which syncs with a hosted DB.
  • Installing ionic pouchdb plugin.
  • Connect our local DB to remote IBM Cloudant DB, a NoSQL hosted DB from IBM.
  • Code a mobile application which will sync with a hosted DB.

What are we learn from the Angular point?

1. FormGroup and Formbuilder from angular forms to validate the form.
  <form [formGroup]="todoForm" (ngSubmit)="addToDo()">
 
 2. Submit button is disable, until all input field in form have inserted.
 <button ion-button block [disabled]="!todoForm.valid" type="submit">Add To Do</button>

What are we building?

We will build a to-do app to which we can add to-dos. These individual to-do items will be stored in the
Cloudant db. If the device is offline or has no connectivity with the Cloudant DB, then the local db is stored
with the updates. Data sync occurs when the Cloudant DB becomes available.

We have to create free account on cloudant.com and create database as 

 

Building our apps:

ionic start OfflineToDo blank

Introduction to IBM Cloudant DB
IBM Cloudant DB is a managed NoSQL JSON database. Following are the features of IBM Cloudant DB:

  • Flexibility–Data is accessible as JSON and provides schema flexibility.
  • Offline apps–Cloudant Sync provides a better, faster user experience.

Installing the PouchDb 
It provides an easy way to replicate the data from a remote db. Following are the features of PouchDB:

  • In-Browser db–Pouch DB can run in various browsers and helps to execute queries faster, as there is no need to execute queries over network.
  • Lightweight–It is a very lightweight db, which can be installed using scripts even in mobile devices.
  • Easy to implement—It is a JavaScript-based db and is easy to implement.
  • Open Source–It is an open source-based db.
  • Document-based database—data is stored in granular data types, with associative mapping of keys to documents.

Step 1:

npm install pouchdb

 

Step 2: Creating data service to connect/retrieve/delete to remote db.

In app folder creates a new folder called provider, add a new file called dataservice.ts. The dataservice provider will contain all code related to the database.

ionic generate provider dataservice

As we need pouchdb and promise for database-related implementation, import the required ones in dataservice.ts file import PouchDB from ‘pouchdb’;

PouchDB from ‘pouchdb‘;
import ‘rxjs/add/operator/toPromise‘;

Add the following line of code in src/providers/dataservice.ts

import { Injectable } from '@angular/core';
import 'rxjs/add/operator/map';
import PouchDB from 'pouchdb';
import 'rxjs/add/operator/toPromise';

@Injectable()

export class Dataservice {
  private db: any ;
  //cloudant username

  private userName = 'n-----n';
  private password= 'blue1---1';
  private dbURL = 'https://n---ten.cloudant.com/tododb';
  //array of todos

  private toDos = [];

  constructor() {
    this.db = new PouchDB('tododb');
      let options = {
        live: true,
        retry: true,
        continuous: true,
        auth: {
        username: this.userName,
        password: this.password
      }
    }
  //Sync the db with Cloudant

  this.db.sync(this.dbURL, options);
  }

  addToDo(doc) {
    return this.db.post(doc);
  }
  deleteToDo(doc) {
    return this.db.remove(doc);
  }

  retrieveToDos(){
    return new Promise<any>(resolve => {
      this.db.allDocs({include_docs: true}).then((result) => {
      if (result.total_rows > 0){
        result.rows.map((row) => {
          this.toDos.push(row.doc);
          resolve(this.toDos);
        });
      }
      else {
        resolve(this.toDos);
      }
      this.db.changes({live: true, since: 'now', include_docs: true}).
        on('change', (change) => {
          this.onChange(change);
        });
      }).catch((error) => {
        console.log(error);
      });
    });
  }

  onChange(change){
    let changedDoc = null;
    let changedIndex = null;
    this.toDos.forEach((doc, index) => {
      if(doc._id === change.id){
        changedDoc = doc;
        changedIndex = index;
      }
    });

    //Handle deleted document

    if(change.deleted){
      this.toDos.splice(changedIndex, 1);
    }
    else {
      //Handle the updates

      if(changedDoc){
        this.toDos[changedIndex] = change.doc;
      }
      //Handle additions

      else {
        this.toDos.push(change.doc);
      }
    }
  }
}

Note:

  1. When the app is initialized, the dataservice constructor is called which will sync the local DB with the remote DB we have tododb database in cloudant.com.
  2. We have specified the authentication for cloudant with login and password.
  3. To add and delete ToDo items add the respective data service methods in the Dataservice class as
    shown in the
      addToDo(doc) {
        return this.db.post(doc);
      }
      deleteToDo(doc) {
        return this.db.remove(doc);
      }
  4. To retrieve Todo items, add the method retrieveToDos(). This method returns a promise which has the “toDos = []” as an array. Also if the data is changed, it will call the method onChange to update the data accordingly.
  5. If the data is updated, we need to update the toDos array accordingly. So the onChange method in onChange(change) accordingly updates by checking the modified doc and the index.

Now that we have the data service layer ready, it’s time to work on the view part.

Step 3: Listing To Do or Creating a view for Todo List

Now we will create a view for our todo apps. In src/pages/home.ts add the following code.

import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Dataservice} from '../../providers/dataservice'; 
import {AddtodoPage} from '../addtodo/addtodo';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
  providers: [Dataservice]
})
export class HomePage {
  public toDos : any[];
  public noToDo : boolean;

  constructor(private todoService: Dataservice, private navController: NavController, private platform: Platform) {
    this.navController = navController;
    this.platform.ready().then(() => {
      this.todoService.retrieveToDos().then(data => {
      this.toDos = data;
      if(this.toDos.length > 0 ) {
        this.noToDo = false;
      }
      else {
        this.noToDo = true;
      }
    })
    .catch(console.error.bind(console));
    });
  }

  showToDoPage() {
	  this.navController.push(AddtodoPage);
  }

  delete(item) {
    this.todoService.deleteToDo(item);
  }
}

Note :

  1. Add our service provider DataService in providers: [Dataservice] in home.ts
  2. Import platform and Dataservice at head of home.ts
  3. In the constructor, we have code to call retrieve the todo list data from DataService.
  4. We can delete the todo list by dragging the list from left to right
  5. We have showToDoPage() method to navigate to the new page to add new to do list on AddtodoPage.

Adding the user identification (UI) to display the list of To Do. Add the following the code in in src/pages/home/home.html.

<ion-header>
  <ion-navbar>
    <ion-title>To Do</ion-title>
    <ion-buttons end>
      <button (click)="showToDoPage()">
        <ion-icon name="add"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content class="home">
  <div *ngIf="noToDo">
    <br/>Click on + to add to do items.
  </div>
  <ion-list inset>
    <ion-item-sliding *ngFor="let toDo of toDos" >
      <ion-item>
        <h2>{{ toDo.name }} - {{ toDo.createdTime | date:'yMMMMd' }}</h2>
        <p class="item-description">{{toDo.description}}</p>
      </ion-item>
      <ion-item-options side="right">
        <button ion-button (click)="delete(toDo)">
          <ion-icon name="ion-trash-a"></ion-icon>Delete
        </button>
      </ion-item-options>
    </ion-item-sliding>
  </ion-list>
</ion-content>
  1. Here we listing all the todo list added to the remote database.
  2. showToDoPage(), To add new todo list by navigate to new page.
  3. We have the delete button to delete the todo list. This sliding option provides a way to delete the To Do

 

 

Step 4: Add a new page for adding new to do list

Add a new page for adding To Do using ionic generate

$ ionic generate page addtodo

We have to import import { Dataservice} from ‘../../providers/dataservice’; import { FormBuilder, FormGroup, Validators } from ‘@angular/forms’;

Add the following code in src/pages/addtodo/addtodo.ts

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Dataservice} from '../../providers/dataservice'; 
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'page-addtodo',
  templateUrl: 'addtodo.html',
  providers: [Dataservice]
})
export class AddtodoPage {
  todoForm: FormGroup;

  constructor(private navController: NavController, private fb: FormBuilder, private todoService: Dataservice) {
    this.todoForm = fb.group({
      'name': ['', Validators.compose([Validators.required,Validators.pattern
      ('[a-zA-Z, ]*'),Validators.minLength(3),Validators.maxLength(100)])],
      'description':['']
    });
  }

  addToDo() {
    let date = new Date();
    let newDoc = {
      'name': this.todoForm.value.name,
      'description':this.todoForm.value.description,
      'createdTime': date.getTime()
    };
    //Add the to do using the data service

    this.todoService.addToDo(newDoc);

    //After the addition navigate to the list view

    this.navController.popToRoot();
  }
  
  ionViewDidLoad() {
    console.log('ionViewDidLoad Addtodo');
  }

}

Add the following code in src/pages/addtodo/addtodo.html

<ion-header>
  <ion-navbar>
    <ion-title>Add To Do</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <form [formGroup]="todoForm" (ngSubmit)="addToDo()">
    <ion-list>
      <ion-item>
        <ion-label color="primary">Name</ion-label>
        <ion-input placeholder="Name" formControlName="name"></ion-input>
      </ion-item>
      <ion-item>
        <ion-label color="primary">Description</ion-label>
        <ion-input placeholder="Description" formControlName="description" ></ion-input>
      </ion-item>
      <ion-item>
        <button ion-button block [disabled]="!todoForm.valid" type="submit">Add To Do</button>
      </ion-item>
    </ion-list>
  </form>
</ion-content>

We can see that ADD TO DO button is disable first, this can be achieved as

<button ion-button block [disabled]="!todoForm.valid" type="submit">Add To Do</button>
  1. When the todForm is empty or when both name and description input have no data inserted then the Add to do button is disabled. When both inputs in the form are entered then the button is enabled.
  2. In form (ngSubmit)=”addToDo() ” we have addToDo to add new to do list to the database.

At last, we have to add reference of AddtodoPage in app.module.ts file and no need to add DataService as a provider, here we can provide DataService as provider directly in the home.ts and addtodo.ts file directly.

import { BrowserModule } from '@angular/platform-browser';

import { HomePage } from '../pages/home/home';
import { AddtodoPage } from '../pages/addtodo/addtodo';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddtodoPage
  ],
  imports: [
...
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddtodoPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Saving Memories – Storing the pictures

Here we building an app that can use the native camera to take a picture and store the image on our mobile device.

What are we learning?

  1. Use camera to snap pictures
  2. Store/retrieve the pictures as in array in/from the device through native storage.
  3. Display the pictures in a carousel

Saving Memories app, which allows us to capture pictures using the native camera of the mobile device. The app also displays all the pictures in a carousel.

ionic start NativeCamera blank
ionic cordova plugin add cordova-plugin-camera
npm install --save @ionic-native/camera

Step 1:

Add the following code in home.html

<ion-header>
  <ion-navbar color="primary">
    <ion-title>Manage your photos!</ion-title>
    <ion-buttons end (click)="takePicture()" class="custom-icon">
      <button><ion-icon name="camera"></ion-icon></button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <div *ngIf="base64Image.length == 0"> <br/><br/>
    Click on the camera icon to take pictures!
  </div>
  <ion-slides pager autoplay="true" pager="true" options="extraOptions" *ngIf="base64Image.length > 0">
    <ion-slide *ngFor="let image of base64Image">
      <img [src]="image" />
    </ion-slide>
  </ion-slides>
</ion-content>

Note:

  1. takePicture() we used the native camera to take photo
  2. Once a picture is captured, we want to store it. To do this, we can choose to store the list of image URIs (uniform resource identifiers) returned by takePicture(). Since it is list of image declare it as array in constructor as this.base64Image = new Array()
  3. <ion-slide *ngFor=”let image of base64Image”> as base64Image have a list of image, here we are displaying one by one through ngFor loop.

Step 2:
Add the following code in home.ts file, we can store the image URI on local storage but it is unreliable, here we are using native storage, which provides persistence and reliable to store the data, in our case image URI in an array.

import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Camera, CameraOptions } from '@ionic-native/camera';
import { NativeStorage } from '@ionic-native/native-storage';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  public extraOptions : {};
  public base64Image: string[];
  
  constructor(public navCtrl: NavController, public platform: Platform, private camera: Camera, private nativeStorage: NativeStorage) {
    this.base64Image = new Array();
    this.extraOptions = {
      pager: true,
      paginationClickable: true,
      spaceBetween: 30,
      centeredSlides: true,
      autoplay: 2000
    }
    this.platform.ready().then(() => {
      this.nativeStorage.getItem("photos").then(data => {
      this.base64Image = data.split(",");
    },
    error => {
      console.log("error in getting photos "+error);
      });
    })
  }

  takePicture(){
    this.camera.getPicture({
      quality : 75,
      destinationType : this.camera.DestinationType.FILE_URI,
      sourceType : this.camera.PictureSourceType.CAMERA,
      allowEdit : true,
      encodingType: this.camera.EncodingType.JPEG,
      targetWidth: 300,
      targetHeight: 300,
      saveToPhotoAlbum: false
    }).then((imageUri) => {
      this.base64Image.push(imageUri);
      let imageUris = this.base64Image.map(o => o).join(', ');
      this.nativeStorage.setItem('photos', imageUris).then(
        () => console.log('Stored item!'),
        error => console.error('Error storing item', error)
      );

      console.log("imageUris is "+imageUris);
    }, (err) => {
      console.log("camera error is"+err);
    });
  }

}

Note:

  1. this.extraOptions we have extra Option property define on ion-slides.
  2. We are using native storage to store all image URI in photos data as array
    this.nativeStorage.setItem('photos', imageUris).then(
            () => console.log('Stored item!'),
            error => console.error('Error storing item', error)
          );
  3. To retrieve store array image from the native we have following code in constructor as
    constructor(public navCtrl: NavController, public platform: Platform, private camera: Camera, private nativeStorage: NativeStorage) {
    ......
        this.platform.ready().then(() => {
          this.nativeStorage.getItem("photos").then(data => {
          this.base64Image = data.split(",");
        },
        error => {
          console.log("error in getting photos "+error);
          });
        })
      }

Add style in home.scss

.custom-icon {
    font-size: 32px;
    color : blue;
}

Step 3: At last add the following line of code in app.module.ts

import { Camera } from '@ionic-native/camera';
import { NativeStorage } from '@ionic-native/native-storage';

@NgModule({

  providers: [
    StatusBar,
    SplashScreen,
    Camera,
    NativeStorage,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

 

Integrating Angular on Node Js

We’ll need to use the TypeScript transpiler to convert our TypeScript files into valid ES5 or ES6 JavaScript files.
Furthermore, since Angular is a front end framework, installing it requires the inclusion of JavaScript files in server or node application.  This can be done in various ways and the easiest one, run ng build and copy the code of dist folder of angular and store them in the public folder.

First, install all the dependency.

We are using mongojs, which provide easy and familiar syntax.

Configure the way we want the TypeScript compiler to run

In order to configure the way TypeScript works, we’ll need to add a new file called tsconfig.json to our application’s root folder. In your new file, paste the following JSON:

{
	"compilerOptions": {
		"target": "es6",
		"module": "system",
		"moduleResolution": "node",
		"sourceMap": true,
		"emitDecoratorMetadata": true,
		"experimentalDecorators": true,
		"removeComments": false,
		"noImplicitAny": false
	},
	"exclude": [
		"node_modules",
		"typings/main",
		"typings/main.d.ts"
	]
}

This file is the root of node folder. When we run our application, the TypeScript will use the tsconfig.json configuration file by default. Next, you’ll need to add a new file called typings.json to your application’s root folder. In your new file, paste the following JSON:


 

Step 1: Creating the backend server. First, we need to create Node backend.

mkdir clientKeeper
npm init 
//Fill all the requirement and set server.js
npm install express body-parser mongojs --save

Now we will create our server.js code as 

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

const app = express();

const port = 3000;

//define db through mongojs

const mongojs = require('mongojs');
const db = mongojs('clientkeeper', ['clients']);

//set Static Folder
app.use(express.static(path.join(__dirname, 'public'))) ;
app.use(bodyParser.json());

app.get('/', (req, res) => {
    res.send('Please use /api/client');
});

app.listen(port, () => {
    console.log('Server start on port' +port);
});

Note in package.json we have to add  “start”: “node server” and we need to run our apps like >> npm start

 

Step 2: Handling Backend

Here we are developing backend, RESTFUL API with complete CRUD functionality.  We will now add code for routing and database. In server.js file add following code for routing before app.listen

//Get Clients - GET request
app.get('/api/clients', (req, res, next) => {
    db.clients.find().sort({first_name:1}, (err, clients) => {
        if(err){
            res.send(err);
        }
        res.json(clients);
    });
});

//Add Client - POST
app.post('/api/clients', (req, res, next) => {
    db.clients.insert(req.body, (err, client) => {
        if(err){
            res.send(err);
        }
        res.json(client);
    });
});

//Update Client - POST
app.put('/api/clients/:id', (req, res, next) => {
    const id = req.params.id;
    db.clients.findAndModify({query: {_id: mongojs.ObjectId(id)},
        update: {
            $set: {
                first_name: req.body.first_name,
                last_name: req.body.last_name,
                email : req.body.email,
                phone: req.body.phone
            }
        },
        new: true
    },(err, client) => {
        res.json(client);    
    });
});

//Add Client - DELETE
app.delete('/api/clients/:id', (req, res, next) => {
    const id = req.params.id;
    db.clients.remove({ _id: mongojs.ObjectId(id)}, (err, client) => {
        if(err){
            res.send(err);
        }
        res.json(client);
    });
});

app.listen(port, () => {
    console.log('Server start on port' +port);
});

Till now we didn’t set up the database, we can test the above code using chrome extension called RestEasy, we can make GET, POST, PUT and DELETE request. For requesting post we have to set header Content-type : application/json.


As what every we type in Rest Easy chrome extension will be inserted in the database, as we are posting data on behaving from the server by our chrome extension.

Step 3: Handling FrontEnd through Angular

We have to install angular cli as npm install -g angular-cli

We will create a project folder and put both node project clienttkeeper and angular project both inside the project folder.  In project folder in command line run following command.

>>ng new ckfrontend
>>cd ckfrontend
>>ng serve
By default angular run on port 4200

Once frontend is complete we will run ng build 
copy dist folder of angular to our public folder of backend.

Inside the app folder in Angular frontend we will create a folder called components in the app folder. Inside the components, folder creates new folder clients. Inside the clients, folder add two more file clients.component.ts and clients.component.html. Add the following code in clients.component.ts file

import { Component } from '@angular/core';

@Component({
  selector: 'clients',
  templateUrl: './clients.component.html'
})
export class ClientsComponent {}

and inside the clients.component.html add the following code.

Hello Clients

We need to declare the clients.component.ts file inside the app.module.ts

Step 4: Creating a Service in FrontEnd to access/fetch data from Backend

Now we will create services file, which will help us to make request to the backend database for the list of clients and will display the client’s information in clients.component.html. Create services folder and add a new file called clients.service.ts. Add the following code in app/services/clients.service.ts . 

import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class ClientService {
    constructor(private http: Http) {}

    getClients() {
        return this.http.get('http://localhost:3000/api/clients')
            .map(res => res.json());
    }
}

We are using HTTP to access the client data, we need to declare HttpModule and ClientService both in app.module.ts.

....
import { HttpModule } from '@angular/http';
......
import { ClientsComponent } from './components/clients/clients.component';
import { ClientService } from './services/clients.service';

@NgModule({
  declarations: [
    AppComponent,
    ClientsComponent
  ],
  imports: [
    BrowserModule,
    HttpModule
  ],
  providers: [ ClientService ],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

In clients.component.ts we are using ClientService so we have to import service in clients.component.ts file.

Add the following code in app/components/clients.component.ts

import { Component, OnInit } from '@angular/core';
import { ClientService } from '../../services/clients.service';

@Component({
  selector: 'clients',
  templateUrl: './clients.component.html'
})

export class ClientsComponent implements OnInit {
  constructor(private clientService: ClientService) {
  }

  ngOnInit() {
    this.clientService.getClients().subscribe(clients => {
      console.log(clients);
    });
  }
}

Note:

  1. We want to run getClients method from ClientService immediately, we can put in the constructor but best and recommended way to do it is used lifecycle method called OnInit. We have to import it in our clients.component.ts file and implement it in our class as in code and ngOnInit() will run immediately with the clients.component.ts class.  Add the following code in app/components/clients.component.ts
  2. this.clientService.getClients().subscribe(clients=> { – the getClients will return Observable, we have to catch it through subscribing , it will give as data.
  3. We have to run both front and back end in command mode. Port 3000 for node and 4200 for angular. On access the client code on browser http://localhost:4200/ we get the error message.

We are getting an error because we request from the different domain, so we have to add middleware in backend or node to allow access from localhost:4200 and add the following code in backend server.js file.

//Allow request from Angular
app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200');
    //Request methods u wish to allow
    
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    //Request header you widht to allow
    
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
    //Pass to next Layer of middleware
   
 next();
})

 

Step 5: Displaying the client list in the template.

We above code, the client will access the data from the backend and display the client information through console.log. Now instead of log to console, we will assign the client data to a variable in component the clients.component.ts file and we can access with the template and display the client in clients.component.html file.

We will modify the clients.component.ts file from log to

export class ClientsComponent implements OnInit {
  clients;
  
  constructor(private clientService: ClientService) {
  }

  ngOnInit() {
    this.clientService.getClients().subscribe(clients => {
      this.clients = clients;
    });
  }
}

Once we assign the client to local variable we can easily access  in template and can loop client data through ngFor. Add the following code in app/components/clients.component.html

<div class="client-list">
    <table class="table table-striped">
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Email</th>
            <th>Phone</th>
            <th></th>
        </tr>
        <tr *ngFor="let client of clients">
            <td>{{clients.first_name}}</td>
            <td>{{clients.last_name}}</td>
            <td>{{clients.email}}</td>
            <td>{{clients.phone}}</td>
            <td><a href="" class="btn btn-default">Edit</a>
 <a href="" class="btn btn-danger">Delete</a></td>
        </tr>
    </table>
</div>

As till now we didn’t apply any style, we can use bootswatch theme spacelab and insert bootstrap link in client index.html as

........
  <base href="/">

  <link rel="stylesheet" href="https://bootswatch.com/spacelab/bootstrap.min.css">
  <meta name="viewport" content="width=device-width, initial-scale=1">
.....

 

In a client or angular side, we create a new component called navbar. Create a folder navbar in app/components and add two file as navbar.component.ts and navbar.component.html file. Inside the navbar.component.ts file add the following code

import { Component} from '@angular/core';

@Component({
  selector: 'navbar',
  templateUrl: './navbar.component.html'
})

export class NavbarComponent {
  constructor() {}
}

 

Add the following line of code in navbar.component.html file

<nav class="navbar navbar-inverse">
    <div class="container">
        <div class="navbar-header">
            <a class="navbar-brand" href="/">ClientKeeper</a>
        </div>
    </div>
</nav>

We also have to import our new navbarcomponent in app.module.ts file. Inside the app/app.component.html file add our new navbar tag.

<navbar></navbar>
<div class="container">
    <clients></clients>
</div>

On accessing the client or angular on browser we will get 

 

 Step 6: Adding new Client information from the front end to back end. 

We will create a form in angular, so we can insert new client from angular to the node. In app/components/clients/client.component.html add the following code for form for adding new client.

<div class="client-form">
    <form (submit)="onAddSubmit()" class="well">
        <h3>Add Client</h3>
        <div class="form-group">
            <label>First Name</label>
            <input type="text" class="form-control" [(ngModel)]="first_name" name="first_name" placeholder="First Name">
        </div>
        <div class="form-group">
            <label>Last Name</label>
            <input type="text" class="form-control" [(ngModel)]="last_name" name="last_name" placeholder="Last Name">
        </div>
        <div class="form-group">
            <label>Email</label>
            <input type="text" class="form-control" [(ngModel)]="email" name="email" placeholder="Email">
        </div>
        <div class="form-group">
            <label>Phone Number</label>
            <input type="text" class="form-control" [(ngModel)]="phone" name="phone" placeholder="Phone Number">
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
</div>
<div class="client-list">
    <table class="table table-striped">
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Email</th>
            <th>Phone</th>
            <th></th>
        </tr>
        <tr *ngFor="let client of clients">
            <td>{{client.first_name}}</td>
            <td>{{client.last_name}}</td>
            <td>{{client.email}}</td>
            <td>{{client.phone}}</td>
            <td><a href="" class="btn btn-default">Edit</a> <a href="" class="btn btn-danger">Delete</a></td>
        </tr>
    </table>
</div>

We have onAddSubmit() on form, this function is called when we click on submit button and we have to define definition for the onAddSubmit() function. Add the following code in app/components/clients/client.component.ts

export class ClientsComponent implements OnInit {
  clients;
    _id;
  first_name;
  last_name;
  email;
  phone;

  constructor(private clientService: ClientService) {
  }

  ngOnInit() {
	...
  }

   onAddSubmit() {
    let newClient = {
      first_name: this.first_name,
      last_name: this.last_name,
      email: this.email,
      phone: this.phone
    }

    this.clientService.saveClient(newClient).subscribe(client => {
     this.clients.push(client);
     this.first_name = '';
     this.last_name = '';
     this.email = '';
     this.phone = '';
   });
   }
}

Note:

  1. We have to define FormModule in app.module.ts and no need to import form module in client.component.ts
  2. We will create new client object in onAddSubmit() {…}
  3. Inside onAddSubmit(){ … we have this.clientService.saveClient(newClient).subscribe(client => { … here we are sending our new client to clientService in app/service folder. Where we will insert new user from client or angular to database in backend.

We need to add saveClient(newClient) method in services/client.service.ts at the end of file

    saveClient(client){
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.post('http://localhost:3000/api/clients', client, {headers: headers})
            .map(res => res.json());
    }

We can insert new client from the front end as in screen shot

Step 7: Adding the edit functionality in FrontEnd

Now we will add edit functionality in client apps. We will create the separate form for adding and editing. We need a way to tell the apps when to use add and edit form.  That can be achieved through setting are a variable called isEdit in our client.component.ts file.  By default set isEdit to false. Add the following code in app/components/clients/clients.component.ts file.

import { Component, OnInit } from '@angular/core';
import {ClientService} from '../../services/client.service';

@Component({
  selector: 'clients',
  templateUrl: './clients.component.html'
})
export class ClientsComponent implements OnInit{
  clients;
  _id;
  first_name;
  last_name;
  email;
  phone;
  isEdit;
  constructor(private clientService: ClientService){
  
  }

 ngOnInit(){
   this.clientService.getClients().subscribe(clients => {
     this.clients = clients;
   });

   this.isEdit = false;
 }

 onAddSubmit(){
   let newClient = {
   ....
 }

 onEditSubmit(){
   let updClient = {
     first_name: this.first_name,
     last_name: this.last_name,
     email: this.email,
     phone: this.phone,
     _id: this._id
   }

   this.clientService.updateClient(updClient).subscribe(client => {
     for(let i = 0; i < this.clients.length;i++){
       if(client._id == this.clients[i]._id){
         this.clients.splice(i, 1);
       }
     }
     this.clients.push(client);
     this.first_name = '';
     this.last_name = '';
     this.email = '';
     this.phone = '';
   });
 }

 onEditClick(client){
   this.isEdit = true;
   this.first_name = client.first_name;
   this.last_name = client.last_name;
   this.email = client.email;
   this.phone = client.phone;
   this._id = client._id;
 }
}
  1. We will call onEditClick(client) method on clicking the edit button on client information in the page
  2. Will call on onEditSubmit() method on clicking on submit of edit form. 
  3. We have service function to update the client information as on this.clientService.updateClient(updClient).subscribe(client => we have to create corresponding function in ClientService. 

In app/components/clients/clients.components.html. We have two ngIf

<div *ngIf="isEdit">
and 
<div *ngIf="!isEdit">

The first ngIf will display the edit form only when isEdit variable value is true that can be set only by clicking on the edit button and the second ngIf will display add new client form as by default isEdit is false. Add the following code in app/components/clients/clients.component.htlm

<div class="client-form">
    <div *ngIf="isEdit">
        <form (submit)="onEditSubmit()" class="well">
            <h3>Edit Client</h3>
            <div class="form-group">
                <label>First Name</label>
                <input type="text" class="form-control" [(ngModel)]="first_name" name="first_name" placeholder="First Name">
            </div>
            <div class="form-group">
                <label>Last Name</label>
                <input type="text" class="form-control" [(ngModel)]="last_name" name="last_name" placeholder="Last Name">
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="text" class="form-control" [(ngModel)]="email" name="email" placeholder="Email">
            </div>
            <div class="form-group">
                <label>Phone Number</label>
                <input type="text" class="form-control" [(ngModel)]="phone" name="phone" placeholder="Phone Number">
            </div>
            <button type="submit" class="btn btn-primary">Submit</button>
        </form>
    </div>

    <div *ngIf="!isEdit">
        <form (submit)="onAddSubmit()" class="well">
            <h3>Add Client</h3>
            <div class="form-group">
                <label>First Name</label>
                <input type="text" class="form-control" [(ngModel)]="first_name" name="first_name" placeholder="First Name">
            </div>
            <div class="form-group">
                <label>Last Name</label>
                <input type="text" class="form-control" [(ngModel)]="last_name" name="last_name" placeholder="Last Name">
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="text" class="form-control" [(ngModel)]="email" name="email" placeholder="Email">
            </div>
            <div class="form-group">
                <label>Phone Number</label>
                <input type="text" class="form-control" [(ngModel)]="phone" name="phone" placeholder="Phone Number">
            </div>
            <button type="submit" class="btn btn-primary">Submit</button>
        </form>
    </div>
</div>
<div class="client-list">
    <table class="table table-striped">
        <tr>
            <th>First Name</th>
        ...
        </tr>
        <tr *ngFor="let client of clients">
            <td>{{client.first_name}}</td>
     ...
            <td>{{client.phone}}</td>
            <td><a (click)="onEditClick(client)" href="#" class="btn btn-default">Edit</a> <a href="" class="btn btn-danger">Delete</a></td>
        </tr>
    </table>
</div>

In template, we are creating click event (click)=onEditClick(client) on anchor edit button.

In app/services/client.service.ts we will add the corresponding code of edit form. Add the following code client.service.ts

    updateClient(client){
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.put('http://localhost:3000/api/clients/'+client._id, client, {headers: headers})
            .map(res => res.json());
    }

Step 8: Adding Delete functionality in the front end.

In app/components/clients/clients.component.html add the following code for delete.

<div class="client-form">
    <div *ngIf="isEdit">
        <form (submit)="onEditSubmit()" class="well">
            <h3>Edit Client</h3>
        ....
    </div>

    <div *ngIf="!isEdit">
        <form (submit)="onAddSubmit()" class="well">
            <h3>Add Client</h3>
        .....
    </div>
</div>
<div class="client-list">
    <table class="table table-striped">
        <tr>
            <th>First Name</th>
...
            <th></th>
        </tr>
        <tr *ngFor="let client of clients">
            <td>{{client.first_name}}</td>
 ...
            <td>{{client.phone}}</td>
            <td><a (click)="onEditClick(client)" href="#" class="btn btn-default">Edit</a> 
            <a (click)="onDeleteClick(client._id)" href="#" class="btn btn-danger">Delete</a></td>
        </tr>
    </table>
</div>

We have add method definition for <a (click)=”onDeleteClick(client._id) in app/components/cleints/clients.component.ts file.

  onDeleteClick(id) {
   this.clientService.deleteClient(id).subscribe(client => {
      for(let i = 0; i < this.clients.length;i++){
        if(id == this.clients[i]._id){
          this.clients.splice(i, 1);
        }
      }
    });
  }

In app/services/client.service.ts we will add the corresponding code of delete. Add the following code client.service.ts

    deleteClient(id){
        return this.http.delete('http://localhost:3000/api/clients/'+id)
            .map(res => res.json());
    }

 

Important: We have complete all the front end code, now we add our angular code in Node. For that, we have run following command at angular cli
>>ng build
The command will build our front end application in dist folder, which we will copy and past in public folder of server or node js. Now run the node server as

>>npm start 

We can use our angular in node server as screen shot.


NOW WE HAVE COMPLETE INTEGRATE FROM ANGULAR TO NODE

As above code are the chunk of the different file, now we will add combine all code in its corresponding file.
Adding all the server code
In Node we have server.js file in root add all it code as

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

const app = express();

const port = 3000;

//define db through mongojs

const mongojs = require('mongojs');
const db = mongojs('clientkeeper', ['clients']);

//set Static Folder
app.use(express.static(path.join(__dirname, 'public'))) ;
app.use(bodyParser.json());

//Allow request from Angular
app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200');
    //Request methods u wish to allow
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    //Request header you widht to allow
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
    //Pass to next Layer of middleware
    next();
    
})



app.get('/', (req, res) => {
    res.send('Please use /api/client');
});

//Get Clients - GET request
app.get('/api/clients', (req, res, next) => {
    db.clients.find().sort({first_name:1}, (err, clients) => {
        if(err){
            res.send(err);
        }
        res.json(clients);
    });
});

//Add Client - POST
app.post('/api/clients', (req, res, next) => {
    db.clients.insert(req.body, (err, client) => {
        if(err){
            res.send(err);
        }
        res.json(client);
    });
});

//Update Client - POST
app.put('/api/clients/:id', (req, res, next) => {
    const id = req.params.id;
    db.clients.findAndModify({query: {_id: mongojs.ObjectId(id)},
        update: {
            $set: {
                first_name: req.body.first_name,
                last_name: req.body.last_name,
                email : req.body.email,
                phone: req.body.phone
            }
        },
        new: true
    },(err, client) => {
        res.json(client);    
    });
});

//Add Client - DELETE
app.delete('/api/clients/:id', (req, res, next) => {
    const id = req.params.id;
    db.clients.remove({ _id: mongojs.ObjectId(id)}, (err, client) => {
        if(err){
            res.send(err);
        }
        res.json(client);
    });
});

app.listen(port, () => {
    console.log('Server start on port' +port);
});

 

Adding all the Client code

First we will add all the code on client component, add the complete code of app/components/clients/clients.component.ts file

import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class ClientService{
    constructor(private http: Http){

    }

    getClients(){
        return this.http.get('http://localhost:3000/api/clients')
            .map(res => res.json());
    }

    saveClient(client){
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.post('http://localhost:3000/api/clients', client, {headers: headers})
            .map(res => res.json());
    }

    updateClient(client){
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.put('http://localhost:3000/api/clients/'+client._id, client, {headers: headers})
            .map(res => res.json());
    }
    
    deleteClient(id){
        return this.http.delete('http://localhost:3000/api/clients/'+id)
            .map(res => res.json());
    }
        
}

Now we will add the complete code of app/components/clients/clients.component.html file

<div class="client-form">
    <div *ngIf="isEdit">
        <form (submit)="onEditSubmit()" class="well">
            <h3>Edit Client</h3>
            <div class="form-group">
                <label>First Name</label>
                <input type="text" class="form-control" [(ngModel)]="first_name" name="first_name" placeholder="First Name">
            </div>
            <div class="form-group">
                <label>Last Name</label>
                <input type="text" class="form-control" [(ngModel)]="last_name" name="last_name" placeholder="Last Name">
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="text" class="form-control" [(ngModel)]="email" name="email" placeholder="Email">
            </div>
            <div class="form-group">
                <label>Phone Number</label>
                <input type="text" class="form-control" [(ngModel)]="phone" name="phone" placeholder="Phone Number">
            </div>
            <button type="submit" class="btn btn-primary">Submit</button>
        </form>
    </div>

    <div *ngIf="!isEdit">
        <form (submit)="onAddSubmit()" class="well">
            <h3>Add Client</h3>
            <div class="form-group">
                <label>First Name</label>
                <input type="text" class="form-control" [(ngModel)]="first_name" name="first_name" placeholder="First Name">
            </div>
            <div class="form-group">
                <label>Last Name</label>
                <input type="text" class="form-control" [(ngModel)]="last_name" name="last_name" placeholder="Last Name">
            </div>
            <div class="form-group">
                <label>Email</label>
                <input type="text" class="form-control" [(ngModel)]="email" name="email" placeholder="Email">
            </div>
            <div class="form-group">
                <label>Phone Number</label>
                <input type="text" class="form-control" [(ngModel)]="phone" name="phone" placeholder="Phone Number">
            </div>
            <button type="submit" class="btn btn-primary">Submit</button>
        </form>
    </div>
</div>
<div class="client-list">
    <table class="table table-striped">
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Email</th>
            <th>Phone</th>
            <th></th>
        </tr>
        <tr *ngFor="let client of clients">
            <td>{{client.first_name}}</td>
            <td>{{client.last_name}}</td>
            <td>{{client.email}}</td>
            <td>{{client.phone}}</td>
            <td><a (click)="onEditClick(client)" href="#" class="btn btn-default">Edit</a> 
            <a (click)="onDeleteClick(client._id)" href="#" class="btn btn-danger">Delete</a></td>
        </tr>
    </table>
</div>

As app/components/navbar and app/app.component.html we are not adding any data as it already added the complete data.

We have to add complete code for app/services/client.service.ts

import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class ClientService{
    constructor(private http: Http){

    }

    getClients(){
        return this.http.get('http://localhost:3000/api/clients')
            .map(res => res.json());
    }

    saveClient(client){
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.post('http://localhost:3000/api/clients', client, {headers: headers})
            .map(res => res.json());
    }

    updateClient(client){
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        return this.http.put('http://localhost:3000/api/clients/'+client._id, client, {headers: headers})
            .map(res => res.json());
    }
    
    deleteClient(id){
        return this.http.delete('http://localhost:3000/api/clients/'+id)
            .map(res => res.json());
    }
        
}

Famous Quotes – Ionic Apps


We are building quotes apps, our apps by default will display all the quotes from one JSON file and we can type to find quotes from a particular author.

What are we learning?

  1. Use HTTP REST call to read a JSON file containing all quotes in quotes.ts.
  2. Sharing the quotes on social networking twitter through cordova-plugin-x-socialsharing.
  3. Uploading the data file i.e quotes.JSON in www/assets/data/quotes.json.
  4. Passing data between different page.
  5. From angular side, we control the search through
     <!-- Displaying all quotes when isfiltered is false -->
     <ion-list *ngIf="!isfiltered">
     
     <!-- Displaying all the filter or one match with search result -->
     <ion-list *ngIf="isfiltered">

 

Let’s build the apps, following all the steps as 

Step 1: Creating the apps through blank template and adding two more page as

>>ionic start QuotesApp blank

Adding two more page
>>ionic generate page quotes-list
>>ionic generate page quotes-detail

 

Step 2: Remove the default home page and replace rootPage value from homePage to QuotesListPage in the app.components.ts

import { Component } from '@angular/core';
.....
import { HttpModule } from '@angular/http';
import { QuotesListPage} from '../pages/quotes-list/quotes-list';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = QuotesListPage;

and we also need to add the reference in app.modules for our two new page and also add SocialSharing provider which we will install the plugin for twitter sharing

import { BrowserModule } from '@angular/platform-browser';
.....
import { QuotesListPage} from '../pages/quotes-list/quotes-list';
import { QuotesDetailPage } from '../pages/quotes-detail/quotes-detail';

@NgModule({
  declarations: [
    MyApp,
    QuotesListPage,
    QuotesDetailPage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    QuotesListPage,
    QuotesDetailPage
  ],
  providers: [
    SocialSharing,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

 

Step 3: Making a REST HTTP Request, our apps will read a JSON file which contains all the quotes and its details. An HTTP call is used to retrieve the JSON file content and loads the JSON to a local variable. We need to import HTTP module and Rxjs (Reactive Extensions Library for JavaScript). This is a library for composing asynchronous and event-based programs using observables. We are including the map operator in our class, which we will use to manage the JSON return. This is used to transform the return object of collection. Add the following code in pages/quotes-list/quotes-list.ts ,

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import {Http} from '@angular/http';
import 'rxjs/add/operator/map';
import {QuotesDetailPage} from '../quotes-detail/quotes-detail';


@Component({
  selector: 'page-quotes-list',
  templateUrl: 'quotes-list.html',
})
export class QuotesListPage {
  quotesList = [];
  filteredQuotes = [];
  isfiltered: boolean ;

  constructor(private http:Http, public navCtrl: NavController, public navParams: NavParams) {
    this.isfiltered = false;
    this.http.get('assets/data/quotes.json')
    .map(res => res.json())
    .subscribe(
      data => {
        this.quotesList = data.quotes;
      },
      err => console.log("error is "+err), // error
      () => console.log('read quotes Complete '+ this.quotesList) // complete
    );
  }

  searchQuotes(event) {
    if(event.target.value.length > 2) {
      var filteredJson = this.quotesList.filter(function (row) {
        if(row.author.indexOf(event.target.value) != -1) {
          return true
        } else {
          return false;
        }
      });
      this.isfiltered = true;
      this.filteredQuotes = filteredJson;
    }
  }

  itemTapped(event, quote) {
    console.log(quote);
    this.navCtrl.push(QuotesDetailPage, {
      quote: quote
    });
  }

}

Note: quotesList is the variable defined to contain the JSON object. filteredQuotes is the variable which will contain the search results. When we click on any one quote, we will navigate to quotes-detail page with full quotes with author information.

Step 4: Adding the search result, now we will add the search bar to search quote through author name.  Add the following code in pages/quotes-list/quotes-list.html 

<ion-header>
  <ion-navbar color="primary">
     <ion-title>Quotes Collection</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-input type="text" placeholder="Search Quotes..." (input)="searchQuotes($event)"></ion-input>

  <ion-list *ngIf="!isfiltered">
    <ion-item *ngFor="let quote1 of quotesList" (click)="itemTapped($event,quote1)">
      <h2>{{quote1.author}}</h2>
      <p class="item-description">{{quote1.quote}}</p>
    </ion-item>
  </ion-list>

  <ion-list *ngIf="isfiltered">
    <ion-item *ngFor="let quote of filteredQuotes" (click)="itemTapped($event,quote)">
      <h2>{{quote.author}}</h2>
      <p class="item-description">{{quote.quote}}</p>
    </ion-item>
  </ion-list>
</ion-content>

The search box when we type more than 2 characters, searchQuotes() is called for searching the JSON object, it will filter the quotes from the quotesList variable. The first ion-list is displayed when the isfiltered value is false, to display the entire list of quotes. The second ion-list is displayed when the isfiltered value is true, to display only the filtered list of quotes.

Step 5: Adding Social Sharing plugin of ionic

$ ionic cordova plugin add cordova-plugin-x-socialsharing
$ npm install --save @ionic-native/social-sharing

Add following code in  pages/quotes-list/quotes-details.html, here we are displaying th quote author and quote in detail and clicking on share twitter will call twitterShare() method to share on twitter apps.

<ion-header>
  <ion-navbar color="primary">
    <ion-title>Quotes Detail</ion-title>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <p>{{quoteDetail.quote}}</p>
  <h4> - {{quoteDetail.author}}</h4>

  <ion-item (click)="twitterShare()">
    <ion-icon>
      <i class="fa fa-twitter" aria-hidden="true" style="color:#1da1f2"></i>- Share on Twitter
    </ion-icon>
  </ion-item>
</ion-content>

Add following code in pages/quotes-list/quotes-details.ts, we are retrieving quote detail and author information through nav params in constructor from the quotes-list.ts page method  itemTapped(event, quote)

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { SocialSharing } from '@ionic-native/social-sharing';

@Component({
  selector: 'page-quotes-detail',
  templateUrl: 'quotes-detail.html',
})
export class QuotesDetailPage {
  quoteDetail: {quote:'', author:''};
  constructor(public navCtrl: NavController, public navParams: NavParams, private socialSharing: SocialSharing) {
    this.quoteDetail = navParams.get('quote');
  }

  twitterShare(){
    console.log("in twitter share");
    let quote: string = this.quoteDetail.quote;
    this.socialSharing.shareViaTwitter(quote.substring(0,110)+"..",null,"http://ionicframework.com/img/homepage/ionicview-icon_2x.png")
    .then((data )=>{
      alert("Success "+data);
    },
    (err)=>{
    alert("failed "+err)
    })
  } //End of the twitterShare
}

 

Step 6: Format of our quotes.JSON in www/assets/data/quotes.json as

{
  "quotes": [
    {
      "quote": "Angular 1 is a framework. Angular 2 is a platform.",
      "author": "Brad Green"
    },
    {
      "quote": "React is a scalpel, Angular is an operating room.",
      "author": "Corinna Cohn"
    }
	.....
	...
	]
}

Angular – Github

We are building Github profile finder, in our app, we have a search bar and based on the name we supply. App will search following information on that particular user

  1. User profile information and avatar
  2. Follower info
  3. Github repository info

Important: As  from Angular point, we are learning ngIf to control the display, at first we set user to false, as this result our page is blank with only search bar and once we type any character it will set user to that character and find information on that user , as we can see on image above.

What we are learning

  1. Retrieving Github API through HTTP get method .
  2. Making service provider
  3. Importing form module for searching information through form

 

Step 1: Getting Github Client Id
To use the Github API, we need to get a client id and client secret, that we will get from github.com/settgins/developer and register for a new application.

Step 2: Setting the App in Angular
ng new githubse
npm install (install the dependency)
npm start
Check on browser on url localhost:4200

Step 3: Creating Profile component

Create a folder components inside the app folder and add new two new files as profile.component.ts and profile.component.html. Inside the profile.component.ts add the following code [app/components/profile.component.ts]

import { Component } from '@angular/core';
import {GithubService} from '../services/github.service';

@Component({
  moduleId: module.id,
  selector: 'profile',
  templateUrl: 'profile.component.html',
})

export class ProfileComponent { 
    user:any;
    repos:any[];
    username: string;
    
    constructor(private _githubService: GithubService){
        this.user = false;
    }

    searchUser(){
        this._githubService.updateUser(this.username);

        this._githubService.getUser().subscribe(user => {
            this.user = user;
        });

        this._githubService.getRepos().subscribe( repos => {
            this.repos = repos;
        });

    }
}

We are importing the GithubService service provider and calling its method through the instance of GithubService and subscribing the observable which we will define in step 5. What we are doing.

  1. Calling Github user information through GithubService method getUser()
  2. Retrieving user repository through getRepos()
  3. Search different user through updating or change new username through updateUser() of GithubService

Step 4: Add the view for profile.component.html, add the following code in [app/components/profile.component.html]. In this code, we add a search bar to retrieve Github user information like profile, avatar, followers and repository details information.

<div class="row">
    <div class="col-md-12">
        <form class="well"> 
            <div class="form-group">
                <input type="text" class="form-control" placeholder="Enter Github Username .." [(ngModel)]="username" name="username" (keyup)="searchUser()">
            </div>
        </form>
    </div>    
</div> <!-- End of row -->

<div *ngIf="user">
   <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">{{user.name}}</h3>
        </div>
        <div class="panel-body">
            <div class="row">
                <div class="col-md-4">
                    <img class="img-thumbnail" src="{{user.avatar_url}}">
                    <a class="btn btn-default btn-block" target="_blank" href="{{user.html_url}}">View Profile</a>
                </div>
                <div class="col-md-8">
                    <div class="stats">
                        <span class="label label-default">{{user.public_repos}} Public Repos</span>
                        <span class="label label-primary">{{user.public_gists}} Public Gists</span>
                        <span class="label label-success">{{user.followers}} Followers</span>
                        <span class="label label-info">{{user.following}} Following</span>
                    </div>
                    <br>
                    <ul class="list-group">
                        <li class="list-group-item"><strong>Username: </strong>{{user.login}}</li>
                        <li class="list-group-item"><strong>Location: </strong>{{user.location}}</li> 
                        <li class="list-group-item"><strong>Email: </strong>{{user.email}}</li> 
                        <li class="list-group-item"><strong>Blog: </strong>{{user.blog}}</li> 
                        <li class="list-group-item"><strong>Member Since: </strong>{{user.created_at}}</li> 
                    </ul>
                </div>    
            </div>
        </div>
    </div>
    
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">User Repos</h3>
        </div>
        <div class="panel-body">
            <div *ngFor="let repo of repos">
                  <div class="row">
                    <div class="col-md-9">
                        <h4><a target="_blank" href="{{repo.html_url}}">{{repo.name}}</a></h4>
                        <p>{{repo.description}}</p>
                    </div>
                    <div class="col-md-3">
                        <span class="label label-default">{{repo.watchers}} Watchers</span>
                        <span class="label label-primary">{{repo.forks}} Forks</span>
                    </div>  
                  </div>
                  <hr>
            </div>
        </div>
    </div>
</div>

 

In app/app.component.ts we will replace following code

@Component({
	.......
	template: '<h1>My First Angular App</h1>
})

To new code, where we add our custom tag profile and wrap in container div and add nav bar code from bootstrap starter code and add the provider as in highlight in the code

import { Component } from '@angular/core';
import { GithubService } from './services/github.service';

@Component({
  selector: 'app-root',
  template: `
     <nav class="navbar navbar-default">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Github Search</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
          <ul class="nav navbar-nav">
           
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav>
    <div class="container">
        <profile></profile>
    </div>`,
        
  styleUrls: ['./app.component.css'],
  providers: [ GithubService ]
})
export class AppComponent {
  title = 'app';
}

 

Step 5: Creating  Service for Github API
We will create Github services so that it can interact with Github API. In the app, folder creates a folder called services, inside this folder add a new file called app/services/github.service.ts. Add the following code

import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class GithubService{
    private username:string;
    private client_id ='386c70968833e0b2c';
    private client_secret = '4af330c6ca939274cee0a5221a8469edd3e';

    constructor(private _http: Http){
        console.log('Github Service Ready...');
        this.username = 'n????p';
    }

    getUser(){
        return this._http
        .get('http://api.github.com/users/' + this.username + '?client_id=' + this.client_id + '&client_secret=' + this.client_secret)
            .map(res => res.json());
    }

    getRepos(){
        return this._http
        .get('http://api.github.com/users/' + this.username + '/repos?client_id=' + this.client_id + '&client_secret=' + this.client_secret)
            .map(res => res.json());
    }

    updateUser(username:string){
        this.username = username;
    }
}

In above code,

  1. we are retrieving user information in JSON format through HTTP module and we have to supply client ID and secret key.
  2. Import injectable so we can create dependency on service into whatever component we need.
  3. Import rxjs, observable with the reactive extension will return the response to observable, then we can subscribe to the observable component to display the information.

Step 6: Define user interface through Bootstrap
Add the following link in src/index.html, this is not best practices.

<!doctype html>
<html lang="en">
<head>
.........

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

 

In app.modules.ts, we have to define imported module, the custom component which we had used in our application.

import { HttpModule } from '@angular/http';
import { FormsModule} from '@angular/forms';

import { ProfileComponent } from './components/profile.component';

@NgModule({
  declarations: [ AppComponent, ProfileComponent],
  imports: [ BrowserModule, HttpModule, FormsModule ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Managing Local User Authentication and Access Control

The user authentication is one of the most important tasks, we need to use authentication in most of our project. Passport is a robust piece of Node.js authentication middleware that helps us to authenticate and access control our Express apps. The Passport middleware allows developers
to offer various authentication methods using a mechanism called strategies, which allows you to
implement a complex authentication layer while keeping your code clean and simple.

The Passport middleware or passport.js allow us to define different strategy. Passport uses strategies to utilize both local authentication and OAuth authentication providers, such as Facebook, Twitter, and Google. Using Passport strategies,
you’ll be able to seamlessly offer different authentication options to your users while maintaining a
unified User model.

What are we learning?

  1. Integrating passport Js in our apps.
  2. Using both local and OAuth authentication strategy.
  3. Understand the serialization to maintains the session.
  4. Integrating Password the Bcrypt

USING PASSPORT’S LOCAL STRATEGY
First, we will learn the passport-local strategy and will learn OAuth authentication later.

Step 1: Installing our project, as we have a lot of dependencies and is better to type all dependency once in the package.JSON and install all together.
npm init Type all the dependency directly in the package.JSON and use npm install. Add the following code in package.JSON and add the line number 8 code also.

{
  "name": "passportlogin",
  "version": "1.0.0",
  "description": "User authentication app",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node app"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express":"*",
    "body-parser":"*",
    "express-handlebars":"*",
    "express-messages":"*",
    "express-session":"*",
    "express-validator":"*",
    "connect-flash":"*",
    "mongoose":"*",
    "passport":"*",
    "passport-http":"*",
    "passport-local":"*",
    "bcryptjs":"*"
  }
}

Step 2: We will create login register functionality first, we will make the user interface through bootstrap later. Inside the views/register.handlebars add the following code for login, registration form. When we fail to login successful, then express validator will display an error message and on successful login, we will display success message at the console.

<form method="post" action="/register">
  <div>
    <label>Name</label>
    <input type="text" name="name" placeholder="Name">
  </div>
  <div>
    <label>Username</label>
    <input type="text" name="username" placeholder="Username">
  </div>
  <div>
    <label>Email</label>
    <input type="email" name="email" placeholder="Email">
  </div>
  <div>
    <label>Password</label>
    <input type="text" name="password" placeholder="Password">
  </div>
  <div>
    <label>Confirm Password</label>
    <input type="text" name="password2" placeholder="Confirm Password">
  </div>
  <button type="submit">Register</button>
  <br>
  Have An Account? <a href="/">Login Now</a>
</form>

Step 3: We have to add code for routes to /register of registration and apply the express validator in routes/index.js as

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

// Home Page
router.get('/', (req, res, next) => {
  res.render('index');
});

// Register Form

router.get('/register', (req, res, next) => {
  res.render('register');
});

// Process Register
router.post('/register', (req, res, next) => {
  const name = req.body.name;
  const username = req.body.username;
  const email = req.body.email;
  const password = req.body.password;
  const password2 = req.body.password2;

  req.checkBody('name', 'Name field is required').notEmpty();
	req.checkBody('email', 'Email field is required').notEmpty();
	req.checkBody('email', 'Email must be a valid email address').isEmail();
	req.checkBody('username', 'Username field is required').notEmpty();
	req.checkBody('password', 'Password field is required').notEmpty();
	req.checkBody('password2', 'Passwords do not match').equals(req.body.password);

  let errors = req.validationErrors();

  if(errors){
    res.render('register', {
      errors: errors
    });
  } else {
    console.log('SUCCESS');
    return;
  }
});

module.exports = router;

Step 4: Creating user model, in root folder add models/user.js and add the following code in user.js, Here we are exporting new model name called User and we are adding hash or encryption of Bcrypt to our password with the salt of 10 characters.

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/passportapp');
const bcrypt = require('bcryptjs');

// User Schema
const UserSchema = mongoose.Schema({
  name: {
    type: String
  },
  username: {
    type: String
  },
  email: {
    type: String
  },
  password: {
    type: String
  }
});

const User = module.exports = mongoose.model('User', UserSchema);

module.exports.registerUser = function(newUser, callback){
  bcrypt.genSalt(10, (err, salt) => {
    bcrypt.hash(newUser.password, salt, (err, hash) => {
      if(err){
        console.log(err);
      }
      newUser.password = hash;
      newUser.save(callback);
    });
  });
}

Step 5: In the index.js in routes, we will adding two new code
1. Add let User = require(‘../models/user’); at line number 3 of routes/index.js
2. Replace the code of console.log(success) of route.post(‘/register’,   to new code for the creating new user,  add route code for login page and once the successful new user is created then we will redirect to /login page.

// Login Form
router.get('/login', (req, res, next) => {
  res.render('login');
});

// Process Register
router.post('/register', (req, res, next) => {
  const name = req.body.name;
  const username = req.body.username;
  const email = req.body.email;
  const password = req.body.password;
  const password2 = req.body.password2;

  req.checkBody('name', 'Name field is required').notEmpty();
	req.checkBody('email', 'Email field is required').notEmpty();
	req.checkBody('email', 'Email must be a valid email address').isEmail();
	req.checkBody('username', 'Username field is required').notEmpty();
	req.checkBody('password', 'Password field is required').notEmpty();
	req.checkBody('password2', 'Passwords do not match').equals(req.body.password);

  let errors = req.validationErrors();

  if(errors){
    res.render('register', {
      errors: errors
    });
  } else {
    const newUser = new User({
      name: name,
      username: username,
      email: email,
      password: password
    });

    User.registerUser(newUser, (err, user) => {
      if(err) throw err;
      req.flash('success_msg', 'You are registered and can log in');
      res.redirect('/login');
    });
  }
});

module.exports = router;

 

Step 5: Inside the views, folder adds a new file login.handlebars with code LOGIN for testing only, when successful adding of new user will redirect to login page. Add new user test as a new user on localhost:3000/register. Check local MongoDB database for a new user as we didn’t create any mongo database, it will create automatically. Check local MongoDB database for a new user as we didn’t create any mongo database, it will create automatically. Check in local mongo shell >show dbs , > use passportapp >> show collection >> db.users.find().pretty()

We can see new user information on the local database.

Note: If we are using handlebar as a template, the middleware Express messages in app.js didn’t work because we can’t write Javascript within the handlebar. So we have to write the code different from the usual and will create the different message for each variable. In the app.js replace express message of

// Express messages
app.use(require('connect-flash')());
app.use((req, res, next) => {
  res.locals.messages = require('express-messages')(req, res);
  next();
});

Replace with new code as

// Express messages
app.use(flash());
app.use((req, res, next) => {
  res.locals.success_msg = req.flash('success_msg');
  res.locals.error_msg = req.flash('error_msg');
  next();
});

 

Step 6: We will add the code for express messages middleware in views/layouts/main.handlerbars to display any message like when login didn’t work.  As we have two type of errors message, one with highlight is from express messages middleware and other without the highlighted is the from the express validator for form. 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Passport App</title>
    <link rel="stylesheet" href="/css/style.css">
  </head>
  <body>
    {{#if success_msg}}
      <div class="alert alert-success">{{success_msg}}</div>
    {{/if}}
    {{#if error_msg}}
      <div class="alert alert-danger">{{error_msg}}</div>
    {{/if}}
    {{#if errors}}
      {{#each errors}}
        <div class="alert alert-danger">{{msg}}</div>
      {{/each}}
    {{/if}}
    {{{body}}}
  </body>
</html>

Step 7: Inside the routes/index.js we will replace the code of process register for a new user to the new code and all other code are same.

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

let User = require('../models/user');

// Home Page
router.get('/', (req, res, next) => {
  res.render('index');
});

// Login Form
router.get('/login', (req, res, next) => {
  res.render('login');
});

// Register Form
router.get('/register', (req, res, next) => {
  res.render('register');
});

// Process Register
router.post('/register', (req, res, next) => {
  const name = req.body.name;
  const username = req.body.username;
  const email = req.body.email;
  const password = req.body.password;
  const password2 = req.body.password2;

  req.checkBody('name', 'Name field is required').notEmpty();
	req.checkBody('email', 'Email field is required').notEmpty();
	req.checkBody('email', 'Email must be a valid email address').isEmail();
	req.checkBody('username', 'Username field is required').notEmpty();
	req.checkBody('password', 'Password field is required').notEmpty();
	req.checkBody('password2', 'Passwords do not match').equals(req.body.password);

  let errors = req.validationErrors();

  if(errors){
    res.render('register', {
      errors: errors
    });
  } else {
    const newUser = new User({
      name: name,
      username: username,
      email: email,
      password: password
    });

    User.registerUser(newUser, (err, user) => {
      if(err) throw err;
      req.flash('success_msg', 'You are registered and can log in');
      res.redirect('/login');
    });
  }
});

module.exports = router;

Step 8: We are able to create a new user or register for new user from above step, now we are adding login form and applying the local strategy. Add the following code for login in views/login.handlebars 

<form method="post" action="/login">
  <div>
    <label>Username</label>
    <input type="text" name="username" placeholder="Username">
  </div>
  <div>
    <label>Password</label>
    <input type="text" name="password" placeholder="Password">
  </div>
  <button type="submit">Login</button>
  <br>
  Don't Have An Account? <a href="/register">Register</a>
</form>

In views/register.handlebars we replace all one code from

Have An Account? <a href="/">Login Now</a>
To
Have An Account? <a href="/login">Login Now</a>

We have to add code for the login post and local strategy, inside routes/index.js. We will add the following code at two place

  1. At the top of routes/index.js
    const express = require('express');
    const router = express.Router();
    const passport = require('passport');
    const LocalStrategy = require('passport-local').Strategy;
  2. at the end or before module.exports = router;
// Local Strategy
passport.use(new LocalStrategy((username, password, done) => {
  User.getUserByUsername(username, (err, user) => {
    if(err) throw err;
    if(!user){
      return done(null, false, {message: 'No user found'});
    }

    User.comparePassword(password, user.password, (err, isMatch) => {
      if(err) throw err;
      if(isMatch){
        return done(null, user);
      } else {
        return done(null, false, {message: 'Wrong Password'});
      }
    });
  });
}));

passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser((id, done) => {
  User.getUserById(id, (err, user) => {
    done(err, user);
  });
});

// Login Processing
router.post('/login', (req, res, next) => {
  passport.authenticate('local', {
    successRedirect:'/',
    failureRedirect:'/login',
    failureFlash: true
  }, (req, res) => {
    res.redirect('/');
  });
});

module.exports = router;

Here we are defining what we will redirect if successful/ unsuccessful login, here in our case we will redirect to a home page on successful user login. On the above code of highlight, we have to define the corresponding function in our model user.

Now we will define login functionality, we will define the highlight function at the end in the models/user.js.

module.exports.getUserByUsername = function(username, callback){
  const query = {username: username}
  User.findOne(query, callback);
}

module.exports.getUserById = function(id, callback){
  User.findById(id, callback);
}

module.exports.comparePassword = function(candidatePassword, hash, callback){
  bcrypt.compare(candidatePassword, hash, (err, isMatch) => {
    if(err) throw err;
    callback(null, isMatch);
  });
}

In app.js we have to add two line of code for password middleware and at below the express session middleware in app.js. 

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const exphbs = require('express-handlebars');
const expressValidator = require('express-validator');
const flash = require('connect-flash');
const session = require('express-session');
const passport = require('passport');
const mongoose = require('mongoose');

const app = express();
const port = 3000;

const index = require('./routes/index');

// View Engine
app.engine('handlebars', exphbs({defaultLayout:'main'}));
app.set('view engine', 'handlebars');

// Static Folder
app.use(express.static(path.join(__dirname, 'public')));

// Body Parser Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// Express Session
app.use(session({
    secret: 'secret',
    saveUninitialized: true,
    resave: true
}));

// Init passport
app.use(passport.initialize());
app.use(passport.session());

// Express messages
app.use(flash());
app.use((req, res, next) => {
  res.locals.success_msg = req.flash('success_msg');
  res.locals.error_msg = req.flash('error_msg');
  next();
});


// Express Validator
app.use(expressValidator({
  errorFormatter: (param, msg, value) => {
      let namespace = param.split('.')
      , root    = namespace.shift()
      , formParam = root;

    while(namespace.length) {
      formParam += '[' + namespace.shift() + ']';
    }
    return {
      param : formParam,
      msg   : msg,
      value : value
    };
  }
}));

app.use('/', index);

// Start Server
app.listen(port, () => {
  console.log('Server started on port '+port);
});

We will define the flash message as a global variable in app.js to displaying the message of login success or fail in views/layouts/main.handlerbar. Append the highlighted code on express message in app.js

// Express messages
app.use(flash());
app.use((req, res, next) => {
  res.locals.success_msg = req.flash('success_msg');
  res.locals.error_msg = req.flash('error_msg');
  res.locals.error = req.flash('error');
  res.locals.user = req.user || null;
  next();
});

In the views/layout/main.handlebars append highlight code on existing code.

!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Passport App</title>
    <link rel="stylesheet" href="/css/style.css">
  </head>
  <body>
<!-- New user registeration msg -->
    {{#if success_msg}}
      <div class="alert alert-success">{{success_msg}}</div>
    {{/if}}
    {{#if error_msg}}
      <div class="alert alert-danger">{{error_msg}}</div>
    {{/if}}
<!-- For login success or failure msg -->
    {{#if error}}
      <div class="alert alert-danger">{{error}}</div>
<!-- Form validation msg -->
    {{/if}}
    {{#if errors}}
      {{#each errors}}
        <div class="alert alert-danger">{{msg}}</div>
      {{/each}}
    {{/if}}
    {{{body}}}
  </body>
</html>

Note: Successful login will take you to root or home page.

Step 9: We have to add logout code and access control on login. First, we will add logout code in routes/index.js after Register form code.

// Register Form
......
// Logout
router.get('/logout', (req, res, next) => {
  req.logout();
  req.flash('success_msg', 'You are logged out');
  res.redirect('/login');
});

Test log out in URL, the first login with validate user login and then type localhost:3000/logout will redirect to the login page with the message as below. 

Now we will apply the access control on login, as for above code till now we can access the home page directly without login. We can control the access as two step

  1. Add the following code at the end of routes/index.js
// Access Control
function ensureAuthenticated(req, res, next){
  if(req.isAuthenticated()){
    return next();
  } else {
    req.flash('error_msg', 'You are not authorized to view that page');
    res.redirect('/login');
  }
}

module.exports = router;

2. Apply the access control on the dashboard or home page. Edit the code of home in routes/index.js


// Home Page
router.get('/', (req, res, next) => {
  res.render('index');
});

To new code as

// Home Page - Dashboard
router.get('/', ensureAuthenticated, (req, res, next) => {
  res.render('index');
});

We will get message while access the dashboard or home page as 

 

Step 10: Now we will apply the user interface through bootstrap, as

We will add the nav bar code from bootstrap example theme, and bootstrap theme link from bootswatch.com Flatly theme copy it URL and the past in views/layouts/main.handlebars

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Passport App</title>
    <link rel="stylesheet" href="https://bootswatch.com/flatly/bootstrap.min.css">
    <link rel="stylesheet" href="/css/style.css"> 
  </head>
  <body>
    
    <nav class="navbar navbar-inverse">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Passport App</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
          <ul class="nav navbar-nav navbar-left">
            <li><a href="/">Dashboard</a></li>
          </ul>

          <ul class="nav navbar-nav navbar-right">
            {{#if user}}
              <li><a href="/logout">Logout</a></li>
            {{else}}
              <li><a href="/login">Login</a></li>
              <li><a href="/register">Register</a></li>
            {{/if}}
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav>
  <div class="container">
    {{#if success_msg}}
      <div class="alert alert-success">{{success_msg}}</div>
    {{/if}}
    {{#if error_msg}}
      <div class="alert alert-danger">{{error_msg}}</div>
    {{/if}}
    {{#if error}}
      <div class="alert alert-danger">{{error}}</div>
    {{/if}}
    {{#if errors}}
      {{#each errors}}
        <div class="alert alert-danger">{{msg}}</div>
      {{/each}}
    {{/if}}
    {{{body}}}
  </div>
  </body>
</html>

Apply the div container class on error message as in highlight code above. As we can easily access the user variable anywhere, which defines global in app.js add the following code in views/index.handlerbars or dashboard as

<h1>Dashboard</h1>
<p>Welcome {{user.name}}</p>

Last, apply the bootstrap class in views/login.handlebars and register.handlebars. First, we will apply bootstrap class on login view as

<form method="post" action="/login">
  <div class="form-group">
    <label>Username</label>
    <input class="form-control" type="text" name="username" placeholder="Username">
  </div>
  <div class="form-group">
    <label>Password</label>
    <input class="form-control" type="password" name="password" placeholder="Password">
  </div>
  <button class="btn btn-primary" type="submit">Login</button>
  <br>
  Don't Have An Account? <a class="btn btn-default btn-sm" href="/register">Register</a>
</form>

and also apply to register view as

<form method="post" action="/register">
  <div class="form-group">
    <label>Name</label>
    <input class="form-control" type="text" name="name" placeholder="Name">
  </div>
  <div class="form-group">
    <label>Username</label>
    <input class="form-control" type="text" name="username" placeholder="Username">
  </div>
  <div class="form-group">
    <label>Email</label>
    <input class="form-control" type="email" name="email" placeholder="Email">
  </div>
  <div class="form-group">
    <label>Password</label>
    <input class="form-control" type="text" name="password" placeholder="Password">
  </div>
  <div class="form-group">
    <label>Confirm Password</label>
    <input class="form-control" type="text" name="password2" placeholder="Confirm Password">
  </div>
  <button class="btn btn-primary"type="submit">Register</button>
  <br>
  Have An Account? <a class="btn btn-default btn-sm" href="/login">Login Now</a>
</form>

 

 

Feedback form in the Express

We will be continuing our previous example with same code, now we will how to added the feedback form on the page. The Express js help us to create the web server with the Javascript.

To create the dynamical web page, where we allowed the client to communicate with the server. Here we are creating API to get or retrieve the client feedback from feedback.json file and allow the client to give feedback as in form.

What we are learning

  1. Adding a new page called feedback page in existing page.
  2. Reading existing feedback data from app/data/feedback.JSON through HTTP GET method.
  3. We will create the Javascript function to manage feedback form.
  4. We learn HTTP method to manage to feedback.JSON data. We can add new user data, delete existing data and retrieve all the user data with api.js file in app/routes.

Step 1:  First we will add feedback page code in app/views/feedback.ejs.

<!DOCTYPE html>
<html>
  <head><% include partials/template/head.ejs %></head>
  <body id="<%= pageID %>">
    <% include partials/template/header.ejs %>
    <div class="container">
      <div class="row">
        <div class="col-sm-4">
          <h1 class="feedback-form-title">Send us feedback</h1>
          <form class="feedback-form">
            <div class="form-group">
              <label for="feedback-form-name">Name</label>
              <input type="text" class="form-control" id="feedback-form-name" placeholder="Enter your name">
            </div>

            <div class="form-group">
              <label for="feedback-form-title">Title</label>
              <input type="text" class="form-control" id="feedback-form-title" placeholder="Title of your feedback">
            </div>

            <div class="form-group">
                <label for="feedback-form-message">Message</label>
                <textarea type="text" placeholder="Enter your message, then hit the submit"  class="form-control" id="feedback-form-message" rows="6"></textarea>
            </div>
            <button type="submit" class="btn btn-default pull-right">Submit</button>
          </form>
        </div><!-- primary -->
        <aside class="col-sm-8">
          <div class="maincontent feedback">
              <h2 class="article-title">Recent Feedback</h2>
              <div class="feedback-messages"></div>
          </div>
        </aside><!-- secondary -->
      </div>
    </div><!-- container -->
    <% include partials/template/jsdefaults.ejs %>
  </body>
</html>

The above code will generate feedback form on the page and retrieve existing data from feedback.json and put the retrieve data on div element having class feedback-messages with help of public/js/feedback.js function.

<div class="feedback-messages"></div>

Step 2: We will have to add routing information to our feedback page.  url : localhost:3000:feedback. Add the following code in app/routes/feedback.js

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

router.get('/feedback', function(req, res) {

  res.render('feedback', {
    pageTitle: 'Feedback',
    pageID: 'feedback'
  });

});

module.exports = router;

Add the following routing information in app/app.js

app.use(require('./routes/feedback'));
app.use(require('./routes/api'));
Step 3: We add the following feedback data in app/data/feedback.json
[
    {
        "name":"T Arun",
        "title":"Best Meetup Ever",
        "message":"I really love this meetup. Please don't let it end."
    },
    {
        "name":"Lie",
        "title":"Meeting Time",
        "message":"Would you consider moving the meeting time 30 minutes to about 6pm. It's tough to make it to the meetings on time right after work."
    },
    {
        "name":"Bhim",
        "title":"Great Speaker",
        "message":"I really enjoyed the speaker this month. Would love to hear another presentation."
    }
]

 

Step 4:  In this web apps example, app/routes/api.js and app/public/js/feedback.js are two most important file, where we manage the feedback form and data in feedback.JSON file.  The api.js allow us to uses HTTP method like GET, POST and DELETE to manage the data in app/data/feedback.JSON file. We can add new user data to feedback.JSON, delete existing user information in feedback.JSON and can retrieve all existing data in feedback.JSON file.

The first we will use HTTP GET method in app.js to retrieve JSON data from app/data/feedback.JSON. In api.js GET method http://localhost:3000/api  will no render like page but  it will retrieve data from api/data/feedback.json and add the following code in app/routes/api.js

var express = require('express');
var router = express.Router();
var bodyParser = require('body-parser');
var fs = require('fs');
var feedbackData = require('../data/feedback.json');

router.get('/api', function(req, res) {
  res.json(feedbackData);
});

router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: false }));

router.post('/api', function(req, res) {
  feedbackData.unshift(req.body);  // To add latest data at top
    fs.writeFile('app/data/feedback.json', JSON.stringify(feedbackData), 'utf8', function(err) {
    if (err) {
      console.log(err);
    }
  });
  res.json(feedbackData);
});


router.delete('/api/:id', function(req, res) {
  feedbackData.splice(req.params.id, 1); // remove one element or feedback message
  fs.writeFile('app/data/feedback.json', JSON.stringify(feedbackData), 'utf8', function(err) {
    if (err) {
      console.log(err);
    }
  });
  res.json(feedbackData);
});

module.exports = router;

With above code, we can route to Visit: localhost:3000/api  will retrieve feedback.JSON data from app/data/feedback.json


We have to install Node.js middleware called body-parser.  The npm install –save body-parser. To handle HTTP POST request in Express.js version 4 and above, you need to install middleware module called body-parser. The body-parser handle the formatting of request data and extract the entire body portion of an incoming request stream and exposes it on req.body. Also, we have to import fs node file system module to work with a file.

Step 5: We also have to add a feedback.js file in routes to render the feedback page.  To url : localhost:3000/feedback and add the following code in app/routes/feedback.js

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

router.get('/feedback', function(req, res) {
  res.render('feedback', {
    pageTitle: 'Feedback',
    pageID: 'feedback'
  });
});

module.exports = router;

 

Step 6: We will create a Javascript function that let us manage data in feedback.json. Inside the public/js/ folder add file called feedback.js.  To manage the feedback form the app/public/js/feedback.js file allows us to perform Javascript event like a form submits, delete and reload the existing feedback data into the web page.

$(function() {
  $.getJSON('api', updateFeedback);
  
  $('.feedback-form').submit(function(e) {
    e.preventDefault();
    $.post('api', {
      name: $('#feedback-form-name').val(),
      title: $('#feedback-form-title').val(),
      message: $('#feedback-form-message').val()
    }, updateFeedback);
  });
  
  $('.feedback-messages').on('click', function(e) {
      if (e.target.className == 'glyphicon glyphicon-remove') {
        $.ajax({
          url: 'api/' + e.target.id,
          type: 'DELETE',
          success: updateFeedback   //will call updateFeedback method to feedback data to page
        }); //ajax
      } // the target is a delete button
  }); //feedback messages
  
  function updateFeedback(data) {   //To load the feedback msg data to page.
   var output = '';
   $.each(data,function(key, item) {
     output += '     <div class="feedback-item item-list media-list">';
     output += '       <div class="feedback-item media">';
     output += '       <div class="media-left"><button class="feedback-delete btn btn-xs btn-danger"><span id="' + key + '" class="glyphicon glyphicon-remove"></span></button></div>';
     output += '         <div class="feedback-info media-body">';
     output += '           <div class="feedback-head">';
     output += '             <div class="feedback-title">' + item.title + ' <small class="feedback-name label label-info">' + item.name + '</small></div>';
     output += '           </div>';
     output += '           <div class="feedback-message">' + item.message + '</div>';
     output += '         </div>';
     output += '       </div>';
     output += '     </div>';
   });
   $('.feedback-messages').html(output);  // Add the output variable result in div oc class name feedback-message in view/feedback.ejs
  }
});

In above code, api is api route and we are using Jquery, with Jquery we can easily handle the ajax request which is how to get data in the page.

  1. We are sending Jquery getJSON request to api in routes folder. In api.js file in routes folder will be sent HTTP GET request to app/data/feedback.JSON. Once all data from app/data/feedback.json are put in output variable containing HTML and data.
  $('.feedback-messages').html(output);

where feedback-messages is the name of div class in file feedback.ejs in views folder.  Here we appending out variable to this div class.

2. When a user clicks on submit button in a feedback form, it will generate an event as e as shown in the code as variable e is an event. We can trap or caught this event e and we can use e.preventDefault(); to prevent the form from reloading in the server. With help of Jquery will send our submitted form data in URL as POST method to our api route. Once form data is sent, we want to call updateFeedback method to load existing feedback message in form.

3. To delete existing feedback message in the page.  To invoke delete event we will click on glyphicon-remove icon. 

The $(‘.feedback-messages’).on(‘click’, function(e) {  

The feedback-messages element in view/feedback.ejs element has the list of all the messages. First, we have to select the target element that is feedback-messages div and then we are sending ajax request with URL and a target id of the element to api route to delete a feedback message. 

 

 

Step 6: We have to set condition in app/views/partials/template/jsdefaults.ejs file, as we will run the app/public/js/feedback.js  Javascript file only when we route to localhost:3000/feedback.  The feedback.js in js folder we will call to api of route and call to api route will return the information from the file feedback.JSON data.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

<% if(typeof artwork !== "undefined") { %>
  <script src="/js/pixgrid.js"></script>
<% } %>


<% if(pageID == 'feedback') { %>
  <script src="/js/feedback.js"></script>
<% } %>

<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
<script src="/reload/reload.js"></script>

 

Ejs – Express apps to retrieve speaker information from data file

E is for effective or embeddable. EJS is a simple templating language that lets you generate HTML markup with plain JavaScript.

We are creating web apps to retrieve speaker and all speaker information from the data .JSON in the data folder and photo from public/images. To use the Ejs we have to install the Ejs through NPM and then we have to tell apps what type of view engine we want to use, this can be achieved with following code

app.set(‘view engine’, ???)

We need to set the location of view engine, by default, it will take main web app folder but we can also change the location with help of the following code.

app.set(‘views’, ???)

The file structure of our web apps as follows. The view is main app folder, we have app, node_modules folder with one package.JSON file.
Inside the app folder, we have four folders and one app.js file. Data folder containing data.JSON file where all information about
the speaker. The file structure of our web apps.

Add the following code in app.js file

var express = require('express');
var reload = require('reload')
var app = express();
var dataFile = require('./data/data.json');

app.set('port', process.env.PORT || 3000); 
app.set('appData', dataFile);
app.set('view engine', 'ejs');
app.set('views', 'app/views');

app.locals.siteTitle = 'Tamo M';
app.locals.allSpeakers = dataFile.speakers;

app.use(express.static('app/public'));
app.use(require('./routes/index'));
app.use(require('./routes/speakers'));

var server = app.listen(app.get('port'), function(){
    console.log('Listening on port' + app.get('port'));
});

reload(server, app);
app.js

 

Add following code in routes/index.js

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


router.get('/', function(req, res){
    var data = req.app.get('appData');
    var pagePhotos = [];
    var pageSpeakers = data.speakers;

    data.speakers.forEach(function(item) {
        pagePhotos = pagePhotos.concat(item.artwork);
    });

    res.render('index',{
        pageTitle: 'Home',
        artwork: pagePhotos,
        speakers: pageSpeakers,
        pageID: 'home'
    });
});

module.exports = router; 

In the first highlight in the code, we are creating a local variable of array type called pagePhotos will collect all the photo of the artists or speaker and then we are setting pagePhotos variable to artwork variable which we can access in index.ejs in views folder.

Note: We have specified all the image name with artists or the speaker name.

In the second highlight line, we have to specify what file to render for root web page /, here we are rendering to index.ejs file of view folder. We don’t have to specify the .ejs extension for index file as we have already specified view engine for views, it is assuming that file is in app/views folder.  We can define a local variable in the app.js which is available to all file in views folder.

app.locals.siteTitle = ‘Tamo M’;

 

In the real web app, we need to pass data along with the template and we want to pass data or variable from route to view.
res.render(‘index’, {
pageTitle: ‘Home’
})

where data or pageTitle become a local variable that we can use inside the view template in the index.ejs. We can also set a variable in app.js, it will be a global version of a variable that will become local variable inside our view. It will apply to all routes in your application.
app.locals.??? = {}

Inside the template, we can access variable by using this code <%= data %>

Add the following code in app/view/index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
  <% include partials/template/head.ejs %>
</head>
<body id="<%= pageID %>">
   <% include partials/template/header.ejs %>
   <% include partials/content/speakerslist.ejs %>


<div class='container'>

    <div id='content' class='row'>
      <div class='col-sm-8'>
        <% include partials/content/maincontent.ejs %>
      </div> <!-- primary -->
      <aside class='col-sm-4'>
        <% include partials/content/whoshouldcome.ejs %>
        <% include partials/content/artworklist.ejs %>
        
      </aside>
    </div>
  </div>
  <% include partials/template/jsdefaults.ejs %>
</body>
</html>

We can define loop and condition in ejs.

Add the following code in app/views/partials/template/jsdefaults.ejs. Here we are checking if variable artwork if it doesn’t exist then don’t load the file pixgrid.js

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

<% if(typeof artwork !== "undefined") { %>
  <script src="/js/pixgrid.js"></script>
<% } %>

<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
<script src="/reload/reload.js"></script>

We will continue this app with adding the client to communicate with the server through the feedback form.