Professional Application Development in MEAN Stack - Part 3
Date Published: 03/04/2018
In this part, we will develop Contact Us page where a user can provide their contact information and ask any query or give a suggestion, this information would be emailed to admin. We will implement Angular Reactive Form and implement email functionality in Node.js.

Download Source on GitHub

Introduction

In part 1 and 2, we already have created an Angular project, developed the base/master and home page. In this part, we will develop contact page, develop the Angular reactive form and set up the server side project. We will use few third-party packages to facilitate our Node.js project to send the email to admin. 

Let's Start

  1. First, go to part 2 and get the project from this Github location and clone it locally. I strongly recommend reading first two parts to understand this one better.
  2. Let's develop the contact page in an Angular project, go to TERMINAL tab from bottom panel and run the command: ng g c client/contact this would create the contact component in an app -> client folder and add its reference in AppModule.
  3. Go to newly created Contact component folder and edit the client -> contact.component.ts. Replace it's content with following: 
    import { Component, OnInit } from '@angular/core';
    import { FormGroup, FormBuilder, Validators } from "@angular/forms";
    import { DataService } from "../../service/data/data.service";
    import { Util } from "../../shared/util";
    
    @Component({
      selector: 'app-contact',
      templateUrl: './contact.component.html',
      styleUrls: ['./contact.component.css']
    })
    export class ContactComponent implements OnInit {
    
      POST_CONTACT: string = "/api/contact";
    
      contactFrm: FormGroup;
    
      constructor(private _fb: FormBuilder, private _dataService: DataService, private _util: Util) { }
    
      ngOnInit() {
    
        this.contactFrm = this._fb.group({
          _id: [''],
          Name: ['', [Validators.required, Validators.maxLength(150)]],
          Phone: ['', [Validators.required]],
          EmailAddress: ['', [Validators.required, Validators.email, Validators.maxLength(250)]],
          Message: ['', [Validators.required, Validators.maxLength(1000)]]
        });
      }
    
      onSubmit(formData: any) {
        delete formData.value._id;
        if (this.contactFrm.invalid) {
          this._util.openSnackBar("Please enter the valid values!", "Error");
        }
        else {
          this._dataService.post(this.POST_CONTACT, formData.value).subscribe(
            data => {
              if (data.success == true) //Success
              {
                this._util.openSnackBar(data.msg, "Success");
              }
              else {
                this._util.openSnackBar(JSON.stringify(data.msg), "Error");
              }
            },
            error => {
            });
        }
      }
    
      resetFrm() {
        this.contactFrm.reset();
      }
    
    }
    
  4. Let's understand above-given code snippet step by step. First, we are importing the required libraries. For reactive forms, we are adding FormGroup, FormBuilder and Validators. If you don't know about Angular reactive or model-driven form, here is a very good tutorial about it. Just a summary, reactive forms give more control, we declare complete form structure, data types and validation in typescript. 
  5. Next, we are creating contactForm and defining form controls, don't worry about _id control for now, we would use it when we will have data in MongoDB, right now, we will just take the user information and send email to website admin. Rest of the controls are self-explanatory, we are also specifying the validation rules for each control, that is the power of reactive forms. Since I am from .NET background, I can easily relate it to ASP.NET MVC strongly typed views.
  6. Next is the onSubmit function that would be called once user would enter all information and hit the Send Message button. The formData object will have all controls values. We are validating the form again to avoid any messing with the values and calling the post method from DataService class that we will create in next steps. You can also see we are calling methods from Util class to open the snack-bar component. We are creating Util class in next steps that for now, would only have openSnackBar function but later we will keep extending it as per need.
  7. Upon response from the server, we will show snack-bar with the message at the bottom of a page that would be stored in the data object. 
  8. The restForm function would be called if a user would press Rest button on contact us page. 
  9. This is all about Contact Us typescript file code, if you didn't understand any point please write below in the comment, I would love to explain it in detail.
  10. Next, let edit the contact us HTML by double click on app -> client -> contact.component.html file. Replace its content with the following: 
    <div>
      <form novalidate (ngSubmit)="onSubmit(contactFrm)" [formGroup]="contactFrm">
        <mat-card>
          <mat-card-header class="header">
            <mat-card-title class="header_title"><i class="material-icons icon_align">contact_mail</i> Contact Us for any Inquiry</mat-card-title>
          </mat-card-header>
          <mat-card-content style="height: 335px;" class="card_cntnt">
            <div class="col-sm-7">
              <div>
                <mat-form-field class="frm-ctrl">
                  <input matInput placeholder="Full Name" formControlName="Name">
                  <mat-error *ngIf="contactFrm.controls['Name'].errors?.required">
                    Name is required!
                  </mat-error>
                </mat-form-field>
              </div>
              <div>
                <mat-form-field class="frm-ctrl">
                  <input matInput placeholder="Email Address" formControlName="EmailAddress">
                  <mat-error *ngIf="contactFrm.controls['EmailAddress'].errors?.required">
                    Email is required!
                  </mat-error>
                  <mat-error *ngIf="contactFrm.controls['EmailAddress'].errors?.email">
                    Please enter a valid email address!
                  </mat-error>
                </mat-form-field>
              </div>
              <div>
                <mat-form-field class="frm-ctrl">
                  <input matInput placeholder="Phone Number" formControlName="Phone">
                  <mat-error *ngIf="contactFrm.controls['Phone'].errors?.required">
                    Phone # is required!
                  </mat-error>
                  <!--   <mat-error *ngIf="contactFrm.controls['EmailAddress'].errors?.email">
                            Please enter a valid phone number!
                          </mat-error> -->
                </mat-form-field>
              </div>
              <div>
                <mat-form-field class="frm-ctrl">
                  <textarea rows="5" matInput placeholder="Message" formControlName="Message"></textarea>
                  <mat-error *ngIf="contactFrm.controls['Message'].errors?.required">
                    Message is required!
                  </mat-error>
                </mat-form-field>
              </div>
            </div>
            <div class="col-sm-5">
              <mat-card>
                <mat-card-header class="subheader">
                  <mat-card-title class="subheader_title"><i class="material-icons icon_align">contact_phone</i> Contact By Phone</mat-card-title>
                </mat-card-header>
                <mat-card-content style="height: 160px;" class="card_cntnt">
                  <div class="col-sm-12">
                    <div class="col-sm-6"><i class="material-icons icon_align">phone</i>
                      <a href="tel:+92 333 5104584"> +92 333 5104584</a>
                    </div>
                    <div class="phone_cntnt  col-sm-6">Mazhar Mahmood, CEO</div>
                  </div>
                  <div class="clearfix" style="padding-bottom: 5px"></div>
                  <div class="col-sm-12">
                    <div class="col-sm-6"><i class="material-icons icon_align">phone</i>
                      <a href="tel:+92 333 5104584"> +92 333 5104584</a>
                    </div>
                    <div class="phone_cntnt  col-sm-6">John Doe, HR</div>
                  </div>
                  <div class="clearfix" style="padding-bottom: 5px"></div>
                  <div class="col-sm-12">
                    <div class="col-sm-6"><i class="material-icons icon_align">phone</i>
                      <a href="tel:+92 333 5104584"> +92 333 5104584</a>
                    </div>
                    <div class="phone_cntnt  col-sm-6">Steve Ric, Sales</div>
                  </div>
                  <div class="clearfix" style="padding-bottom: 5px"></div>
                  <div class="col-sm-12">
                    <div class="col-sm-6"><i class="material-icons icon_align">phone</i>
                      <a href="tel:+92 333 5104584"> +92 333 5104584</a>
                    </div>
                    <div class="phone_cntnt  col-sm-6">Jhon D., Help Desk</div>
                  </div>
                </mat-card-content>
              </mat-card>
            </div>
          </mat-card-content>
          <mat-card-actions style="padding-bottom: 10px" class="card_footer">
            <button color="warn" type="button" (click)="resetFrm()" mat-raised-button>Reset</button>&nbsp;
            <button type="submit" color="primary" [disabled]="contactFrm.invalid" mat-raised-button>Send Message</button>
          </mat-card-actions>
        </mat-card>
      </form>
    </div>
  11. Just go through the HTML code and I am sure that typescript code will start making sense to you. On the top, you would see, we are starting the form body. The novalidate avoids HTML validation because each browser has a different way of showing HTML validation messages. So, in order to keep the consistency, we are sticking to our custom error messages. 
  12. Next, we are using Angular Event Binding to tell form to call onSumbit method when a user submits the form. We already understood the onSumbit method functionality. The last statement tells the form that formGroup is contactFrm that we created in typescript, this is Reactive form syntax.
  13. In the form's body, we are using my favorite mat-card control from Angular Material, setting the contact form header along some icon to make it attractive.
  14. In the mat-card's content, I am again using Angular Material Form Controls; mat-form-field and matInput. I find them very attractive and easy to use components but if you want to use any other controls, please don't hesitate. You can see Angular Material provides very clean and readable coding for each control. e.g. mat-error clearly has a condition that if Name form control has required error, just show the message. Rest of the code is same, I left the Phone # validation for now but we will come back to it in later parts.
  15. At the bottom, we have two buttons, one is of type submit that will cause submit event and other is of button type to reset the form. The [disabled]="contactFrm.invalid" condition tells the Angular form to disable the Send Message button until user enters all required and valid values in form controls. If you always want to enable the Send Message button, just delete this statement. The mat-raised-button is from Angular Material button styles.
  16. Now, let's add the CSS for Contact Us page, edit the app -> client -> contact -> contact.component.css file and replace its content with following: 
    mat-card
    {
      margin: 0px;
      padding: 0px;
    }
    
    mat-card-noshadow{ 
        background: #ECECF4;
        box-shadow: none !important;
    }
    
    mat-card-content{
      margin: 0px;
      padding: 0px;
    }
    
    .header
    {
    background: #45484d;
    border-bottom: 5px solid #393B3E;
    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: #A1A9AF;
    }
    
    .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;
    }
    
  17. These are almost same classes I have explained in previous parts so let's don't waste our time on them. 
  18. Again, don't hesitate to contact me for any further clarification of any code. Please remember, our code is still not ready to Run so ng serve -o will throw an error. We need more steps to make our project runnable.
  19. As you saw, we are using the Reactive form in Contact Us page, we need to add it's reference in AppModule. Edit the app -> app.module.ts file and add the Form reference like following: 
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    //Reactive Forms Reference
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    
    //In Import Module Section
    
    imports: [
        FormsModule,
        ReactiveFormsModule,
        .....
        .....
  20. Now that we have created the Contact Page, let's add it to the routing table and update its link in AppComponent. Edit the app -> app-routing.module.ts and add a contact page path, your final routing table should be as follow:  
    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";
    
    const routes: Routes = [
      {
        path: '',
        redirectTo: 'home',
        pathMatch: 'full'
      },
      {
        path: 'home',
        component: HomeComponent,
      },
    
    // Contact page Path
      {
        path: 'contact',
        component: ContactComponent,
      }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    
  21. Edit the app.component.html and update the contact link as follow, we are only telling that when contact link is click, just call the contact path and in routing table, we have specified if the path is contact, redirect it to the ContactComponent
    <li><a [routerLink]="['contact']">Contact</a></li>
  22. Next, let's create the DataService that will POST the contact information to the server that is Node.js in our case. Run ng g s service/data/data -m app.module.ts command in TERMINAL. You would see that a new data.service.ts file has been created in app -> service -> data folder. In future, we will also create Auth Service for user authentication that we will put in service -> auth folder. Also, the DataService reference is automatically added in AppModule's providers section to make it Injectable. That means we don't need to create the DataService object in every component, all we need to is to create a DataService variable in component's constructor and AppModule will automatically inject the object for us, this is how the Dependency Injection is achieved in Angular. If you are currently working in ASP.NET MVC Core and previously used Niject library in .NET, this shouldn't be a new thing for you.
  23. Edit the newly created app -> service -> data -> data.service.ts file and replace its content with following: 
    import { Injectable } from '@angular/core';
    import { Observable } from "rxjs/Observable";
    import { Http, Response, Headers, RequestOptions } from '@angular/http';
    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/do';
    import 'rxjs/add/operator/catch';
    import { environment } from "../../../environments/environment";
    
    @Injectable()
    export class DataService {
    
      SERVER_URL = environment.api_url;
    
      constructor(public _http_unsecure: Http) { }
    
      post(url: string, model: any): Observable<any> {
        let body = JSON.stringify(model);
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });
        return this._http_unsecure.post(this.SERVER_URL+url, body, options)
          .map((response: Response) => <any>response.json())
      }
    
    }
    
  24. You can see in above code, service is nothing but just a class with Injectable() header that has pretty obvious meaning, eligibility for dependency injection. We are adding HTTP and helper classes to call the HTTP POST, GET, PUT and DELETE functions.
  25. One important thing you can see in above code is an environment reference, we can create the different environment.json files for different environments e.g. DEV, STAGING, PRODUCTION etc. Each file will have same variables but with environment specific values. Once we will create a build with a command: ng build --prod, our build command will take PRODUCTION environment file otherwise normal (DEV) one. We will create PRODUCTION environment file in next steps.
  26. In a constructor, we are creating the HTTP type object, _http_unsecure that is used to make HTTP calls. I am keeping the name _http_unsecure for a reason. We will have few secure and insecure request depending upon our requirement e.g. in the admin section, to add a new menu item or page, a user must be logged in. For that, we would use token-based authentication and need to use different HTTP service. Right now, don't worry about it, we will come back to DataService service in upcoming parts.
  27. Right now, we are having only one POST method that is taking endpoint URL, input data and returning Observable of any type. You can read about Observer from here. Let me explain my version step by step:
    1. First what the heck is this Observable? Let's understand it by above implementation. Observable is a function that takes an observer that has next, complete and error methods. When we say this function is returning Observable<User>, it means the Observable will call .next() method of an observer and pass it a User object value or error if there is any.
    2. Observable emits values from the producer if some event happens e.g. you can return Observable<value> on mouse click, keyboard input or in our case the event is occurring when getting the response back from a server. So our POST function producer is a POST function's response from the server.
    3. When Observable gets any value from the producer, it informs observer take it and keep flowing until there is an error or no values from a producer (means it is completed). 
    4. So in order to get the value from Observable, we subscribe it by explicitly calling the .subscribe() method. But there is one more interesting concept, As I told you in previous steps, an observer takes the value by .next() method and start flowing, this value travels/can travel through a chain of observable by operators. Example operators are .map(), .filter() etc.
    5. In above example you can see in POST function's return statement, we are using .map() operator and converting the response into JSON, the map operator will also return Observable (we are not using it though) and so on until we will call .subscrible() method to get the final result. 
    6. Go to app -> client -> contact.component.ts file, in onSubmit() function you will find we are subscribing to POST's method result and getting the data. 
    7. You can declare as many Observable variables and by calling observer .next() method it can travel throughout the application and can be canceled anytime. 
    8. One powerful feature of Observable is subscription can be canceled anytime if no more data is required as compared to promises where either the full result or error message is returned. Let me know if you have any question regarding Observable. 
  28. As you can see in above DataService, we are using the Http class, we need to add it to our AppModule, edit the app -> app.module.ts file and add the Http reference like following:  
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    //Http Module For GET, POST, PUT and DELETE RESTful APIs
    import { HttpModule } from '@angular/http';
    
    //Add in in imports section
    
     imports: [
        HttpModule,
        FormsModule,
        ReactiveFormsModule,
        ....
        ....
  29. We also talked about environment file for different environments in DataService, let's also create one environment file for PRODUCTION and update both files.
  30. Right click on src -> environments folder and select option, New File. Enter the file name environment.prod.ts. Now, open the existing environment.ts file and read the comment on top. You would understand why we created the PRODUCTION file with a specific name.
  31. In environment.ts file, you should see production: false. Add one more entry for api_url that we are using in DataService. Your final environment file should look like following: 
    // The file contents for the current environment will overwrite these during build.
    // The build system defaults to the dev environment which uses `environment.ts`, but if you do
    // `ng build --env=prod` then `environment.prod.ts` will be used instead.
    // The list of which env maps to which file can be found in `.angular-cli.json`.
    
    export const environment = {
      production: false,
      api_url:"http://localhost:3000"
    };
    
  32. Now edit the environment.prod.ts file and replace its content with following:  
    export const environment = {
      production: true,
      api_url:"https://fullstackcircle.com"
    };
    
  33. So, this means when we will do normal build by the command: ng serve -o it will take my normal environment file with api_url as http://localhost:3000 and when we would create build by the command: ng build --prod, it will use environment.prod.ts and use api_url as https://fullstackcircle.com and that's what we want since once we will create an Angular project build, put it in node.js project and deploy it in server, all APIs should point to production URL. We will learn it in detail in upcoming parts so don't worry if you didn't get it now.
  34. Don't hesitate to put the environment specific variables in the environments file. Angular CLI build command is intelligent enough to take care of the environment by build command's parameters. 
  35. Next, let's create an interface model for the contact page. We are not using it now but in future, we may use it for admin page to manage all contacts. So what is the purpose of defining a model for the contact page? In a model, we can define the datatype of each form element and map the form data to it to easily access it. You would see it in action in future. Run the command: ng g i model/contact and replace the content with following: 
    export interface Contact {
        _id:string;
        Name:string;
        Phone:string,
        EmailAddress:string,
        Message:string
    }
    
  36. You would see, all model elements are matching to form element. Right now, you can park it. We will learn about it in future.
  37. next, you can see in app -> contact -> contact.component.ts file, we are using import { Util } from "../../shared/util"; statement to include util.ts file. Right now, we have only one function in util to show the snackbar control at bottom of the page to show successful or error page from the server. We are creating this Util class as a service so that we don't have to create its object in every component. Run the command: ng g s shared/util -m app.module.ts. It will create the Util service and adds its reference in AppModule's providers section to be injectable in any component. 
  38. Edit the shared -> util.ts file and replace its content with following: 
    import { Injectable } from "@angular/core";
    import { MatSnackBar } from "@angular/material";
    
    @Injectable()
    export class Util {
        constructor(private snackBar: MatSnackBar) { }
    
        openSnackBar(message: string, action: string): void {
            let dur = action == ("Error" || "Pending") ? -1 : 2000;
            this.snackBar.open(message, action, {
                duration: dur,
            });
        }
    }
    
  39. So pretty straightforward, we have one function openSnackBar, taking the message to be displayed along the action. We are only checking if Error or Pending is passed as action, keep the snackbar open until a user clicks on it to close it otherwise hide it after 2 seconds. Check the app -> contact.component.ts file, how we are using it.
  40. In the TERMINAL tab, enter the command: ng serve -o to build and open the application in the browser. Click on Contact menu item, you should be able to see a page and check the validation. In future, you would see this page in action when we will implement server-side code.
  41. That's pretty much on client-side development since this article is becoming a little longer, let's cover the server development section in next part. Let me know if you have any issue with development. 



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