Professional Application Development in MEAN Stack - Part 8
Date Published: 10/14/2018
In earlier parts, we created the Contact Us page to get the user feedback, the user message is only sent in an email to the site admin, in this part, we will also save the message in the database and create the Admin page to manage them, we will use the same Data Grid control we created in the previous part and this time we will only provide the delete message functionality.

Introduction

In this part, we will create the admin section for the Contact Us page where we can view the user messages and delete them. This would be a short article as compared to the previous one and you would see the same Data Grid in action that we created in the previous part and used in Menu Mangement

Let's Start

It is strongly recommended to read all previous parts before moving to this one, can't promise you would understand all the steps I am going to list down without knowledge of previous parts. 

  1. In early parts, the Contact Us page was only sending an email to the admin, in this part, we will also save it in the database and create the GET, POST and DELETE APIs, there would be no PUT API since it doesn't make sense to update the user message. So let's update the APIs in the node.js Server application.
  2. Before jumping into the coding, run the mazharncoweb Client application by ng server -o command and server application by pm2 start environment.json --env development command. Make sure everything is working fine with no issue. Keep the website running so that you can see the live updates.
  3. In the Server application, edit the server -> routes -> contact.js and replace its contents with the following: 
    const express = require('express');
    const router = express.Router();
    const Contact = require('../models/contactMdl');
    
    router.get('/contact', function (req, res) {
      Contact.find({}, function (err, contacts) {
        if (err) {
          res.json({ success: false, msg: err });
        } else {
          res.json({ success: true, data: contacts });
        }
      }).sort({ OrderBy: 1 });
    });
    
    router.post('/contact', function (req, res) {
      let cntctObj = new Contact(req.body);
      cntctObj.DateAdded = new Date();
      cntctObj.DateUpdated = new Date();
      cntctObj.save(function (err) {
        if (err) {
          res.json({ success: false, msg: "There is some error in sending message!" });
          return;
        } else {
          Contact.sendContactEmail(req.body.Name, req.body.EmailAddress, req.body.Message,
            function (error, info) {
              if (error) {
                res.json({ success: false, msg: error });
              } else {
                res.json({ success: true, msg: "Thank you for contacting, we will get to you back soon!" });
              }
            }
          );
        }
      });
    });
    
    router.delete('/contact/:id', function (req, res) {
      let query = { _id: req.params.id }
      Contact.findById(req.params.id, function (err) {
        Contact.remove(query, function (err) {
          if (err) {
            res.json({ success: false, msg: err });
            return;
          } else {
            res.json({ success: true, msg: "Successfully deleted the message!" });
          }
        });
      });
    });
    
    module.exports = router;
    
  4. Here, you can see we add the GET API that is loading all the message from the database without a filter. The POST API is extended to have database insertion logic along the send email method. The DELETE API is first looking for a record from database against ID sent from the Client application and deleting the record by ID.  
  5. Make sure server -> models -> contactMdl.js has all database fields and looks like the following: 
    const mongoose = require('mongoose');
    const emailer = require('../shared/email');
    // Contact Schema
    
    const ContactSchema = mongoose.Schema({
      Name: {
        type: String,
        required: true
      },
      Phone: {
        type: String,
        required: true
      },
      EmailAddress: {
        type: String,
        required: true
      },
      Message: {
        type: String,
        required: true
      }
    });
    
    const Contact = module.exports = mongoose.model('Contact', ContactSchema);
    
    
    module.exports.sendContactEmail = function (name, email, message, callback) {
      let msgplain = name + ' has sent you a message ' + email + '-' + message;
      let msghtml = name + ' has sent you a message <br>' + 'Email Address: ' + email + '<br><br>Message: ' + message;
      emailer.sendEmail(process.env.FROM_EMAIL, '', 'New Message Received from Mazhar & Co.', msgplain, msghtml, callback);
    }
  6. That's all on the server side, run the command on the server terminal: pm2 restart mazharnco  --update-env to update the changes, to make sure your updates are working, test the http://localhost:3000/api/contact URL in Postman (we learned how to use the Postman in previous parts)
  7. Now move to Client application mazharncoweb, let's do the small modification and update the Contact Interface name from Contact to IContact, not required but I like to keep the things according to conventions, edit the src -> app -> model -> contact.ts and replace its content with the following: 
    //Updated the Contact Interface name from "Contact" to "IContact"
    export interface IContact {
        _id:string;
        Name:string;
        Phone:string,
        EmailAddress:string,
        Message:string
    }
    
  8. Next, let's create the UserMessage admin component to view and delete the messages. We don't need ManageMesssage component since we are not adding and updating the user messages. In the client application terminal (right click on a mazharncoweb folder and select the option Open in Terminal) and run the command: ng g c admin/UserMessage
  9. Edit the app -> admin -> user-message -> user-message.component.css and add the following CSS in it: 
    mat-card
    {
      margin: 0px;
      padding: 0px;
    }
    
    mat-card-noshadow{ 
        background: #ECECF4;
        box-shadow: none !important;
    }
    
    mat-card-content{
      margin: 0px;
      padding: 0px;
    }
    
    .header
    {
    background: #E90C0C;
    border-bottom: 5px solid #790606;
    height: 50px;
    padding-left: 5px;
    }
    
    .subheader
    {
    height: 40px;
    background: #5B5C60;
    }
    
    .subheader_title{
        vertical-align:baseline;
        padding-top: 5px;
        padding-bottom: 0px;
        padding-left: 5px;
        font-size: 13pt;
        font-family: Arial, Helvetica, sans-serif;
        color: #C8CCD2;
    }
    
    .header_title{
        vertical-align:baseline;
        padding-top: 10px;
        padding-bottom: 0px;
        padding-left: 5px;
        font-size: 16pt;
        font-family: Arial, Helvetica, sans-serif;
        color: #FFFFFF;
    }
    
    .card_cntnt
    {
        padding: 15px;
        padding-bottom: 15px;
    }
    
    .card_footer
    {
        text-align: left;
        padding-left: 40px;
    }
    
    .frm-ctrl {
        width: 90%;
    }
    
    .icon_align
    {
        vertical-align: middle;
    }
    .phone_cntnt
    {
        font-weight: bold;
    }
    
  10. Nothing new, it is same old CSS we are using in previous parts components, you can change the color, font size and style according to your convenience.
  11. Edit the app -> admin -> user-message -> user-message.component.html and replace its content with the following: 
    <div>
      <mat-card>
        <mat-card-header class="header">
          <mat-card-title class="header_title"><i class="material-icons">message</i> Message Management</mat-card-title>
        </mat-card-header>
        <mat-card-content class="card_cntnt">
          <div>
            <app-datagrid
            [displayedColumns]="displayedColumns"
            [filterPlaceholder]="filterPlaceholder"
            [data]="data"
            (btnclick)="deleteContact($event)"
            ></app-datagrid>
          </div>
        </mat-card-content>
      </mat-card>
    </div>
    <button [routerLink]="['../../admin']" mat-button color="primary">Back to Menu</button>
  12. In the above HTML, we are using the Data Grid component created in the previous part, you can see it is almost the same as MenuManagement component, this is a great example of reusability. 
  13. Edit the app -> admin -> user-message -> user-message.component.ts and replace its content with the following:  
    import { Component, OnInit } from '@angular/core';
    import { DBOperation, ResponseSnackbar } from "../../shared/enum";
    import { IContact } from "../../model/contact";
    import { DataService } from "../../service/data/data.service";
    import { MatDialog } from "@angular/material";
    import { Util } from "../../shared/util";
    
    @Component({
      selector: 'app-user-message',
      templateUrl: './user-message.component.html',
      styleUrls: ['./user-message.component.css']
    })
    export class UserMessageComponent implements OnInit {
    
      dbops: DBOperation;
      modalTitle: string;
      modalBtnTitle: string;
      contact: IContact;
      data: any;
      url: string = "/api/contact";
      delete_url:string = "/api/contact/id";
    
      displayedColumns: any[] = [
        {
          display: 'Name',
          variable: 'Name',
          type: 'text'
        },
        {
          display: 'Phone',
          variable: 'Phone',
          type: 'text'
        },
        {
          display: 'EmailAddress',
          variable: 'EmailAddress',
          type: 'text'
        },
        {
          display: 'Message',
          variable: 'Message',
          type: 'text'
        },
        {
          display: '',
          variable: 'Delete',
          action: DBOperation.delete,
          type: 'btn'
        }
      ];
    
      filterPlaceholder: string = "Search Message";
      constructor(private _dataService: DataService, private dialog: MatDialog, private _util: Util) {
       
      }
    
      ngOnInit() {
        this.loadData();
      }
    
      loadData() {
        this.data = this._dataService.get(this.url).map(data => data.data);
      }
    
      deleteContact(gridaction: any)
      {
        this._util.confirmDelete().subscribe(result => {
          if (<boolean>result==true)
              {
                this._dataService.delete(this.delete_url.replace("id",gridaction.row._id)).subscribe(
                  data => {
                      if (data.success == true) //Success
                      {
                        this._util.openSnackBar(data.msg,ResponseSnackbar.Sucess);
                        this.loadData();
                      }
                      else {
                        this._util.openSnackBar(JSON.stringify(data.msg),ResponseSnackbar.Error);
                      }
                  },
                  error => {
                  });
              }
        });
    
      }
    }
    
  14. In the above code, the loadData() function is calling the contact GET API we created in initial steps of this article and tested in Postman, the result is saved in data variable that we are passing as an argument to Data Grid component (check the HTML). The displayColumns variable has all the fields we want to display on data Grid along the Delete button at the end. This is also an argument to Data Grid component. The deleteContact() method is calling the Delete API we created earlier and removing the selected record from the database, you can see we are passing the record ID as an argument to Delete API. This ID is being returned by gridaction objects from Data Grid, you can see the in user-message.component.html, the deleteComponent() is an output event that is triggered when the user clicks on Delete button in the Data Grid. 
  15. The last step is to add the newly created UserMassage component route in the app-routing.module.ts, edit the src -> app -> app-routing.module.ts file and replace its content with the following:   
    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    import { HomeComponent } from "./client/home/home.component";
    import { ContactComponent } from "./client/contact/contact.component";
    import { ViewPageComponent } from "./client/view-page/view-page.component";
    import { AdminComponent } from './admin/admin/admin.component';
    import { MenuListComponent } from './admin/menu/menu-list/menu-list.component';
    import { UserMessageComponent } from './admin/user-message/user-message.component';
    
    const routes: Routes = [
      {
        path: '',
        redirectTo: 'home',
        pathMatch: 'full'
      },
      {
        path: 'home',
        component: HomeComponent,
      },
      {
        path: 'contact',
        component: ContactComponent,
      },
      {
        path: 'page/:id',
        component: ViewPageComponent,
      },
      {
        path:'admin',
        component: AdminComponent
      },
      {
        path:'admin/managemenu',
        component: MenuListComponent,
      }
      ,
      {
        path:'admin/usermsg',
        component:UserMessageComponent
      },
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    
  16. We added the admin/usermsg path to UserMessageComponent page we created. That's it. Run the application if it not already running, go to URL: http://localhost:4200/admin and click the User Messages link, you should land to Message Management page, test the page by deleting the existing message if any, go to http://localhost:4200/contact and create new messages, make sure they appear in newly created Message Management page and you are able to delete them. 

Keywords: Angular Reactive Form tutorial, Node.js nodeemailer, MEAN stack tutorial, Angular 5 tutorial for beginners, MEAN Stack tutorial for beginners, Rxjs tutorial or beginner, Rxjs vs Promise,nodemailer,pm2, Node.js MongoDB, Node.js mongoose configuration, Angular Data Grid Control, Free Angular Data Grid Control