Fullstack Hub

[Count: 1]

In this part, we will develop a 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 the admin. We will implement an Angular Reactive Form and implement email functionality in Node.js.

Introduction

In parts 1 and 2, we already have created an Angular project, developed the base/master and home page. In this part, we will develop a contact page, develop the Angular reactive form, and set up the server-side project. We will use a few third-party packages to facilitate our Node.js project to send the email to the 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 the first two parts to understand this one better.
  2. Let’s develop the contact page in an Angular project, go to the TERMINAL tab from the 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 the newly created Contact component folder and edit the client -> contact.component.ts. Replace its content with the following:
TypeScript
  1. Let’s understand the above-given code snippet step by step. First, we are importing the required libraries. For reactive forms, we are adding FormGroupFormBuilder 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. 
  2. 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. The rest of the controls are self-explanatory, we are also specifying the validation rules for each control, which is the power of reactive forms. Since I am from the .NET background, I can easily relate it to ASP.NET MVC strongly typed views.
  3. Next is the onSubmit a function that would be called once the user would enter all information and hit the Send Message button. The formData object will have all control values. We are validating the form again to avoid any messing with the values and calling the post method from DataService a class that we will create in the next steps. You can also see we are calling methods from Util class to open the snack-bar component. We are creating Util a class in the next steps that for now, would only have openSnackBar function but later we will keep extending it as per need.
  4. Upon response from the server, we will show the snack-bar with the message at the bottom of a page that would be stored in the data object. 
  5. The restForm function would be called if a user would press the Rest button on the contact us page. 
  6. 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.
  7. 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> 
        <button type="submit" color="primary" [disabled]="contactFrm.invalid" mat-raised-button>Send Message</button>
      </mat-card-actions>
    </mat-card>
  </form>
</div>
  1. 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. 
  2. Next, we are using Angular Event Binding to tell the form to call onSumbit a 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.
  3. 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.
  4. 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. The rest of the code is the same, I left the Phone # validation for now but we will come back to it in later parts.
  5. At the bottom, we have two buttons, one is of the type submit that will cause submit an event and the 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 the 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.
  6. 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;
}
  1. These are almost the same classes I have explained in previous parts so let’s don’t waste our time on them. 
  2. 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.
  3. 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 the following: 
TypeScript
  1. 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:  
TypeScript
  1. 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>
  1. 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 the 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.
  2. Edit the newly created app -> service -> data -> data.service.ts file and replace its content with the following:
TypeScript
  1. You can see in the above code, service is nothing but just a class with Injectable() header that has a pretty obvious meaning, eligibility for dependency injection. We are adding HTTP and helper classes to call the HTTP POST, GET, PUT and DELETE functions.
  2. One important thing you can see in the 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 the same variables but with environment-specific values. Once we will create a build with a command: ng build --prod, our build command will take the PRODUCTION environment file otherwise normal (DEV) one. We will create a PRODUCTION environment file in the next steps.
  3. 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 a few secure and insecure requests 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.
  4. 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 the Observer from here. Let me explain my version step by step:
    1. First what the heck is this Observable? Let’s understand it by the above implementation. Observable is a function that takes an observer that has nextcomplete 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, we subscribe to 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() the method and start flowing, this value travels/can travel through a chain of observable by operators. Example operators are .map().filter() etc.
    5. In the above example, you can see in the POST function’s return statement, we are using .map() the 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() the 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
  5. As you can see 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 a reference like the following: 
TypeScript
  1. We also talked about environment file for different environments in DataService, let’s also create one environment file for PRODUCTION and update both files.
  2. 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.
  3. 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 the following:
TypeScript
  1. Now edit the environment.prod.ts file and replace its content with the following: 
TypeScript
  1. 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 the 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.
  2. 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 building the command’s parameters. 
  3. Next, let’s create an interface model for the contact page. We are not using it now but in the future, we may use it for an 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 the future. Run the command: ng g i model/contact and replace the content with the following:
TypeScript
  1. You would see, all model elements are matching to form elements. Right now, you can park it. We will learn about it in the future.
  2. 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 a 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 provider’s section to be injectable in any component. 
  3. Edit the shared -> util.ts file and replace its content with the following:
TypeScript
  1. So pretty straightforward, we have one function openSnackBar, taking the message to be displayed along with 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.
  2. In the TERMINAL tab, enter the command: ng serve -o to build and open the application in the browser. Click on the Contact menu item, you should be able to see a page and check the validation. In the future, you would see this page in action when we will implement server-side code.
  3. That’s pretty much on client-side development since this article is becoming a little longer, let’s cover the server development section in the next part. Let me know if you have any issues with development. 

Yaseer Mumtaz

Leave a Reply

Your email address will not be published. Required fields are marked *