Angular Bootstrap Contact Form

Angular Contact Form - Bootstrap 4 & Material Design

Note: This documentation is for an older version of Bootstrap (v.4). A newer version is available for Bootstrap 5. We recommend migrating to the latest version of our product - Material Design for Bootstrap 5.
Go to docs v.5

This Angular Bootstrap Contact Form tutorials is a step-by-step guide on how to create a contact form for sending messages using the NodeJS environment and Express server. It also covers the validation of client-side and server-side messages.

Requirements:

  • Angular 6 with Angular CLI,
  • The 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 some 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:

1. Create a new Angular application using Angular CLI:

        
            
          ng new mdb-contact-form --style=scss
        
        
    

Create a new Connection Service:

        
            
          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 the MDB Angular Free version, use the code for the first form. If you are using MDB Angular Pro, copy the second forms code 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 -->
                  <mdb-checkbox [default]="true" class="mb-4">Send me a copy of this message</mdb-checkbox>
                  <!-- 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;

                  @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
                        mdbValidate
                      />
                      <label for="materialContactFormName">Name</label>
                      <mdb-error *ngIf="name.invalid && (name.dirty || name.touched)"
                        >Input invalid</mdb-error
                      >
                      <mdb-success *ngIf="name.valid && (name.dirty || name.touched)"
                        >Input valid</mdb-success
                      >
                    </div>
                    <div class="md-form">
                      <input
                        type="email"
                        formControlName="contactFormEmail"
                        id="materialContactFormEmail"
                        class="form-control"
                        mdbInput
                        mdbValidate
                      />
                      <label for="materialContactFormEmail">E-mail</label>
                      <mdb-error *ngIf="email.invalid && (email.dirty || email.touched)"
                        >Input invalid</mdb-error
                      >
                      <mdb-success *ngIf="email.valid && (email.dirty || email.touched)"
                        >Input valid</mdb-success
                      >
                    </div>
                    <span>Subject</span>
                    <div class="row">
                      <div class="col-md-12 mx-auto">
                        <div class="md-form">
                          <mdb-select
                            formControlName="contactFormSubjects"
                            [options]="optionsSelect"
                            placeholder="Choose your option"
                            mdbValidate
                          ></mdb-select>
                          <mdb-error *ngIf="subjects.invalid && (subjects.dirty || subjects.touched)"
                            >Input invalid</mdb-error
                          >
                          <mdb-success *ngIf="subjects.valid && (subjects.dirty || subjects.touched)"
                            >Input valid</mdb-success
                          >
                        </div>
                      </div>
                    </div>
                    <div class="md-form">
                      <textarea
                        type="text"
                        formControlName="contactFormMessage"
                        id="materialContactFormMessage"
                        class="form-control md-textarea"
                        mdbInput
                        mdbValidate
                      ></textarea>
                      <label for="materialContactFormMessage">Message</label>
                      <mdb-error *ngIf="message.invalid && (message.dirty || message.touched)"
                        >Input invalid</mdb-error
                      >
                      <mdb-success *ngIf="message.valid && (message.dirty || message.touched)"
                        >Input valid</mdb-success
                      >
                    </div>
                    <div class="row">
                      <div class="col-md-6 mx-auto d-flex justify-content-center">
                        <div class="md-form">
                          <mdb-checkbox mdbValidate formControlName="contactFormCopy"
                            >Send me a copy of this message</mdb-checkbox
                          >
                          <mdb-error *ngIf="copy.invalid && (copy.dirty || copy.touched)"
                            >Input invalid</mdb-error
                          >
                          <mdb-success *ngIf="copy.valid && (copy.dirty || copy.touched)"
                            >Input valid</mdb-success
                          >
                        </div>
                      </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, 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 = [
                    { value: 'Feedback', label: 'Feedback' },
                    { value: 'Report a bug', label: 'Report a bug' },
                    { value: 'Feature request', label: 'Feature request' },
                    { value: 'Other stuff', label: 'Other stuff' },
                  ];

                  @HostListener('input') oninput() {
                    if (this.contactForm.valid) {
                      this.disabledSubmitButton = false;
                    }
                  }

                  constructor(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': ['', Validators.requiredTrue],
                    });
                  }

                  get name() {
                    return this.contactForm.get('contactFormName');
                  }

                  get email() {
                    return this.contactForm.get('contactFormEmail');
                  }

                  get subjects() {
                    return this.contactForm.get('contactFormSubjects');
                  }

                  get message() {
                    return this.contactForm.get('contactFormMessage');
                  }

                  get copy() {
                    return this.contactForm.get('contactFormCopy');
                  }

                  onSubmit() {
                    this.connectionService.sendMessage(this.contactForm.value).subscribe(() => {
                      alert('Your message has been sent.');
                      this.contactForm.reset();
                      this.disabledSubmitButton = true;
                    }, (error: any) => {
                      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 implements the client-side validation.

In the ngOnInit lifecycle, we populate the optionsSelect variable with values that will serve 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 details 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

To do this, 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, the provider (if it is gmail), port, authorization, and many other things that are to be sent. To make it easier, you can send emails from a Gmail account. To do this, enter your username and password to your mailbox in the user and pass keys.

Line 22 — app.use(bodyParser.json()); launches the 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 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 of the 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. The 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 end.

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.

        
            
            import { WavesModule, ButtonsModule, CardsModule, InputsModule } from 'ng-uikit-pro-standard';
            import { FormsModule, ReactiveFormsModule } from '@angular/forms';
            import { HttpClientModule } from '@angular/common/http';
          
        
    
        
            
            import { WavesModule, ButtonsModule, CardsModule, InputsModule } from 'angular-bootstrap-md';
            import { FormsModule, ReactiveFormsModule } from '@angular/forms';
            import { HttpClientModule } from '@angular/common/http';