Angular Bootstrap contact form

Angular Contact Form - Bootstrap 4 & Material Design

Angular Bootstrap Contact Form - a step-by-step guide on how to create a contact form for sending messages using the NodeJS environment and Express server. Validation of client-side and server-side messages.

Requirements:

  • Angular 6 with Angular CLI,
  • NodeJS environment,
  • The Express package from the npm repository,
  • The nodemailer package from the npm repository,
  • The body-parser package from the npm repository.

If you need a ready to download code, you will find it here.


Creating the application and the necessary service

Let's start from scratch, - let's create a new application and generate a service responsible for contact with our NodeJS server. In order to do this, execute the commands below in your application terminal:

      
        ng new mdb-contact-form --style=scss
      
    
      
        ng generate service connection
      
    

Then add the newly generated ConnectionService to the providers array in the app.module.ts file.


Note!

Within this tutorial we are using the Material Design for Bootstrap library, you can download it for free from here. Without the library the form will still work however it may look and behave differently. It's recommended to use this library along with the tutorial.

Creating the form

If you are using MDB Angular Free version, use the code of the first form. If you are using MDB Angular Pro, copy the second form into your application.

Default form contact

Contact us



              <!-- Default form contact -->
              <form class="text-center border border-light p-5" [formGroup]="contactForm" (ngSubmit)="onSubmit()">

                <p class="h4 mb-4">Contact us</p>

                <!-- Name -->
                <input type="text" formControlName="contactFormName" id="defaultContactFormName" mdbInput
                  class="form-control mb-4" placeholder="Name">

                <!-- Email -->
                <input type="email" formControlName="contactFormEmail" id="defaultContactFormEmail" mdbInput
                  class="form-control mb-4" placeholder="E-mail">

                <!-- Subject -->
                <label>Subject</label>
                <select formControlName="contactFormSubjects" class="browser-default custom-select mb-4">
                  <option value="" disabled>Choose option</option>
                  <option value="1" selected>Feedback</option>
                  <option value="2">Report a bug</option>
                  <option value="3">Feature request</option>
                  <option value="4">Feature request</option>
                </select>

                <!-- Message -->
                <div class="form-group">
                  <textarea formControlName="contactFormMessage" class="form-control rounded-0" mdbInput id="exampleFormControlTextarea2"
                    rows="3" placeholder="Message"></textarea>
                </div>

                <!-- Copy -->
                <div class="custom-control custom-checkbox mb-4">
                  <input formControlName="contactFormCopy" type="checkbox" class="custom-control-input" id="defaultContactFormCopy">
                  <label class="custom-control-label" for="defaultContactFormCopy">Send me a copy of this message</label>
                </div>

                <!-- Send button -->
                <button mdbBtn color="info" outline="true" block="true" class="z-depth-0 my-4 waves-effect"
                  mdbWavesEffect type="submit" [disabled]="disabledSubmitButton">Send</button>

              </form>
              <!-- Default form contact -->


            

              import { ConnectionService } from './connection.service';
              import { FormGroup, FormBuilder, Validators } from '@angular/forms';
              import { Component, HostListener } from '@angular/core';

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

              contactForm: FormGroup;
              disabledSubmitButton: boolean = true;
              optionsSelect: Array<any>;

                @HostListener('input') oninput() {

                if (this.contactForm.valid) {
                  this.disabledSubmitButton = false;
                  }
                }

                constructor(private fb: FormBuilder, private connectionService: ConnectionService) {

                this.contactForm = fb.group({
                  'contactFormName': ['', Validators.required],
                  'contactFormEmail': ['', Validators.compose([Validators.required, Validators.email])],
                  'contactFormSubjects': ['', Validators.required],
                  'contactFormMessage': ['', Validators.required],
                  'contactFormCopy': [''],
                  });
                }

                onSubmit() {
                  this.connectionService.sendMessage(this.contactForm.value).subscribe(() => {
                    alert('Your message has been sent.');
                    this.contactForm.reset();
                    this.disabledSubmitButton = true;
                  }, error => {
                    console.log('Error', error);
                  });
                }

                }
            

Material form contact MDB Pro component

Contact us
Subject


              <mdb-card>

                <mdb-card-header class="info-color white-text text-center py-4">
                  <h5>
                    <strong>Contact us</strong>
                  </h5>
                </mdb-card-header>

                <mdb-card-body class="px-lg-5 pt-0">

                  <form class="text-center" style="color: #757575;" [formGroup]="contactForm" (ngSubmit)="onSubmit()">

                    <div class="md-form mt-3">
                      <input type="text" formControlName="contactFormName" id="materialContactFormName" class="form-control"
                        mdbInput>
                      <label for="materialContactFormName">Name</label>
                    </div>

                    <div class="md-form">
                      <input type="email" formControlName="contactFormEmail" id="materialContactFormEmail" class="form-control"
                        mdbInput>
                      <label for="materialContactFormEmail">E-mail</label>
                    </div>

                    <span>Subject</span>
                    <div class="row">
                      <div class="col-md-12 mx-auto">
                        <mdb-select formControlName="contactFormSubjects" [options]="optionsSelect" placeholder="Choose your option"></mdb-select>
                      </div>
                    </div>

                    <div class="md-form">
                      <textarea type="text" formControlName="contactFormMessage" id="materialContactFormMessage" class="form-control md-textarea"
                        mdbInput></textarea>
                      <label for="materialContactFormMessage">Message</label>
                    </div>

                    <div class="row">
                      <div class="col-md-6 mx-auto d-flex justify-content-center">
                        <mdb-checkbox formControlName="contactFormCopy">Send me a copy of this message</mdb-checkbox>
                      </div>
                    </div>

                    <button mdbBtn color="info" outline="true" rounded="true" block="true" class="z-depth-0 my-4 waves-effect"
                      mdbWavesEffect type="submit" [disabled]="disabledSubmitButton">Send</button>

                  </form>

                </mdb-card-body>

              </mdb-card>

            

              import { ConnectionService } from './connection.service';
              import { FormGroup, FormBuilder, Validators } from '@angular/forms';
              import { Component, OnInit, HostListener } from '@angular/core';

              @Component({
                selector: 'app-root',
                templateUrl: './app.component.html',
                styleUrls: ['./app.component.scss'],
              })
              export class AppComponent implements OnInit {

              contactForm: FormGroup;
              disabledSubmitButton: boolean = true;
              optionsSelect: Array<any>;

                @HostListener('input') oninput() {

                  if (this.contactForm.valid) {
                    this.disabledSubmitButton = false;
                  }
                }

                constructor(private fb: FormBuilder, private connectionService: ConnectionService) {

                this.contactForm = fb.group({
                  'contactFormName': ['', Validators.required],
                  'contactFormEmail': ['', Validators.compose([Validators.required, Validators.email])],
                  'contactFormSubjects': ['', Validators.required],
                  'contactFormMessage': ['', Validators.required],
                  'contactFormCopy': [''],
                  });
                }

                ngOnInit() {

                this.optionsSelect = [
                  { value: 'Feedback', label: 'Feedback' },
                  { value: 'Report a bug', label: 'Report a bug' },
                  { value: 'Feature request', label: 'Feature request' },
                  { value: 'Other stuff', label: 'Other stuff' },
                  ];
                }

                onSubmit() {
                  this.connectionService.sendMessage(this.contactForm.value).subscribe(() => {
                    alert('Your message has been sent.');
                    this.contactForm.reset();
                    this.disabledSubmitButton = true;
                  }, error => {
                    console.log('Error', error);
                  });
                }

                }
            

After copying the above code to your application you will see a ready to use form. Let me briefly describe what exactly the above component code does.

In the constructor we define all fields of the form. All fields except contactFormCopy are marked as required, which means that without filling in these fields, we will not be able to send the form. This is the client-side validation.

In the ngOnInit lifecycle we populate the optionsSelect variable with values that will serve us as message subjects that the user will be able to select in the form.

The onSubmit() method is responsible for calling the sendMessage() method defined in the ConnectionService service. This method is responsible for sending our form to the backend.


Building the ConnectionService logic

The ConnectionService is responsible for contact with our backend written in NodeJS. It is not yet created, but we will do it in the next step. Let's now fill in the ConnectionService with the appropriate code, which will allow you to send the form to the backend.


        import { HttpClient, HttpHeaders } from '@angular/common/http';
        import { Injectable } from '@angular/core';

        @Injectable({
        providedIn: 'root'
        })
        export class ConnectionService {
        url: string = 'http://localhost:3000/send';
        constructor(private http: HttpClient) { }

        sendMessage(messageContent: any) {
          return this.http.post(this.url,
          JSON.stringify(messageContent),
          { headers: new HttpHeaders({ 'Content-Type': 'application/json' }), responseType: 'text' });
        }
        }
      

The sendMessage() method is responsible for hitting the http://localhost:3000/send address, which is the router defined in the Express server that we will immediately create. Thanks to this, our application knows how to send data between the frontend and backend layers.

The post() method of the HttpClient class takes three parameters.

The first parameter is the address of the endpoint to be hit by the front layer.

The second parameter is the data that the front has to send to the address from the first parameter.

The third parameter are the options, which we can pass to the request. In them we pass the Http header with the defined Content-Type parameter.


Creating the NodeJS backend server file

Create a new file in your application's root directory, and name it server.js, then paste the code you will find below. Without any worries, I'll immediately describe what the code you copy is doing.

If you haven't installed the express, nodemailer and body-parser packages from the npm repository so far, do so immediately using the following command in your application terminal:

    
      npm install express nodemailer body-parser --save
    
  

        const express = require('express');
        const nodemailer = require('nodemailer');
        const app = express();
        const port = 3000;
        const bodyParser = require('body-parser');

        const transporter = nodemailer.createTransport({

          host: 'smtp.gmail.com',
          provider: 'gmail',
          port: 465,
          secure: true,
          auth: {
            user: ' ', // Enter here email address from which you want to send emails
            pass: ' ' // Enter here password for email account from which you want to send emails
          },
          tls: {
          rejectUnauthorized: false
          }
        });

        app.use(bodyParser.json());

        app.use(function (req, res, next) {

          res.header("Access-Control-Allow-Origin", "*");
          res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
          next();
        });

        app.post('/send', function (req, res) {

          let senderName = req.body.contactFormName;
          let senderEmail = req.body.contactFormEmail;
          let messageSubject = req.body.contactFormSubjects;
          let messageText = req.body.contactFormMessage;
          let copyToSender = req.body.contactFormCopy;

          let mailOptions = {
            to: [' '], // Enter here the email address on which you want to send emails from your customers
            from: senderName,
            subject: messageSubject,
            text: messageText,
            replyTo: senderEmail
          };

          if (senderName === '') {
            res.status(400);
            res.send({
            message: 'Bad request'
            });
            return;
          }

          if (senderEmail === '') {
            res.status(400);
            res.send({
            message: 'Bad request'
            });
            return;
          }

          if (messageSubject === '') {
            res.status(400);
            res.send({
            message: 'Bad request'
            });
            return;
          }

          if (messageText === '') {
            res.status(400);
            res.send({
            message: 'Bad request'
            });
            return;
          }

          if (copyToSender) {
            mailOptions.to.push(senderEmail);
          }

          transporter.sendMail(mailOptions, function (error, response) {
            if (error) {
              console.log(error);
              res.end('error');
            } else {
              console.log('Message sent: ', response);
              res.end('sent');
            }
          });
        });

        app.listen(port, function () {
          console.log('Express started on port: ', port);
        });

      

I will start the description of the above code from the top.

Line 7 - const transporter = nodemailer.createTransport({ }) is used to create a function that takes the object containing the configuration of the transporter as a parameter. It is in this configuration that you define the host from which emails are sent, provider (if it is gmail), port, authorization and many other things are to be sent. To make it easier, you can send emails from gmail account. To do this, enter your username and password to your mailbox in user and pass keys.

Line 22 - app.use(bodyParser.json()); launches body-parser, thanks to which we can intercept data sent in the requests from the frontend layer.

Lines 24 - 29 are only used in development environments. It is not recommended to use them in a production environment unless you know exactly what you are doing.

In line 31 we define route /post, after which the frontend layer sends data to the backend layer. This is where the actual email is sent to the mailbox.

Lines 33 - 37 create helper variables, thanks to which we can more easily refer to the individual components of our request.

line 39 defines the mailOptions object that is sent to the sendMail() method. It is in this object that we define, to who the email is to be sent, from who, what is its subject, text, and to who the response in the email is to be addressed. In this object there are many more options that you can use. You will find them all on this page.

Lines 47 - 77 are responsible for server-side validation. This is a very simple validation, which only checks if all required fields are given. If any of them is not given, the backend returns the message to the front layer that the request is incorrect.

Lines 79 - 81 correspond to whether a copy of the message should be sent to the user. In the form this is determined by the checkbox.

Lines 83 - 89 are responsible for mail sending. This is where the sendMail() function with the parameters defined above is called. If an error occurs, the server console will register the log. If the shipment is successful, the whole message will be returned to the server console.

Lines 92 - 94 run our Express server on the port 3000, which we defined at the beginning of the file.


Tutorial conclusion

In this tutorial I presented a very simple way to send messages from the contact form on the front, through the use of a very popular backend stack NodeJS + Express. Example application, which you wrote together with me, can be easily extended by e.g. more advanced validation on the server side, and other, interesting things on the front side.

If you have any problems while working with this tutorial, please look at our support forum and see if anyone has already had a similar problem.

If you encountered a problem with Google blocking your application while sending an email from @gmail, try changing permissions for less secure applications. You can achieve this by visiting this page.

Angular Contact Form - API

In this section you will find informations about required modules of the Angular Contact Form.


Modules used

In order to speed up your application, you can choose to import only the modules you actually need, instead of importing the entire MDB Angular library. Remember that importing the entire library, and immediately afterwards a specific module, is bad practice, and can cause application errors.

// MDB Angular Pro
import { WavesModule, ButtonsModule, CardsModule, InputsModule } from 'ng-uikit-pro-standard'
// MDB Angular Free
import { WavesModule, ButtonsModule, CardsModule, InputsModule } from 'angular-bootstrap-md'
// Angular Forms
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
// Angular HttpClient
import { HttpClientModule } from '@angular/common/http'