Contact form

Angular Bootstrap 5 Contact form component

A typical subscription form used when subscribing to a newsletter, asking a question in a FAQ section, or leaving a testimonial / review for a product.

To learn more read Docs.


Prerequisites

Before starting the project make sure to install the following utilities:

  • Node LTS environment (12.x.x recommended)
  • Express- setting the server
  • Express-validator - for validating the form
  • Nodemailer - for sending mails
  • Multer - for handling multiparts request (reading FormData)
  • Dotenv - for loading environment variables
  • Cors - for enabling CORS (Cross-origin resource sharing)

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.

Form HTML

First, create basic contact form which will be our base for validating and sending data.

Contact us

Copy and paste the following HTML and JavaScript code into your file (e.g. index.html):

        
            
        <section class="border p-4 d-flex justify-content-center mb-4">
          <form id="form" class="text-center" style="width: 100%; max-width: 300px">
            <h2>Contact us</h2>
      
            <!-- Name input -->
            <mdb-form-control class="mb-4">
              <input mdbInput type="text" id="name" class="form-control" />
              <label mdbLabel class="form-label" for="name">Name</label>
            </mdb-form-control>
            <!-- Email input -->
            <mdb-form-control class="mb-4">
              <input mdbInput type="email" id="email" class="form-control" />
              <label mdbLabel class="form-label" for="email">Email address</label>
            </mdb-form-control>
            <!-- Subject input -->
            <mdb-form-control class="mb-4">
              <input mdbInput type="text" id="subject" class="form-control" />
              <label mdbLabel class="form-label" for="subject">Subject</label>
            </mdb-form-control>
            <!-- Message input -->
            <mdb-form-control class="mb-4">
              <textarea mdbInput class="form-control" id="message" rows="4"></textarea>
              <label mdbLabel class="form-label" for="message">Message</label>
            </mdb-form-control>
            <!-- Mail copy -->
            <div class="form-check">
              <input
                mdbCheckbox
                class="form-check-input"
                type="checkbox"
                value=""
                id="copy"
              />
              <label class="form-check-label" for="copy">
                Send me copy
              </label>
            </div>
            <!-- Submit button -->
            <button type="button" class="btn btn-primary my-4">Send</button>
      
          </form>
        </section>
        
        
    

Frontend validation

Our form now works fine. However currently if the user makes a mistake by clicking send without filling in the form first, this will result in sending an empty email. The other potential problem is that user might make a mistake in his email address so he would never get a response from us.

MDB Validation

MDBootstrap form components provide built-in validation which can be used to validate user data before sending email via PHP script. Read more about MDBootstrap form validation here.

In the example below all inputs were marked as required, and JavaScript code enabling custom MDB Form Validation was added.

Contact us

        
            
        <section class="border p-4 d-flex justify-content-center mb-4">
          <form [formGroup]="validationForm" id="form" class="text-center" style="width: 100%; max-width: 300px">
            <h2>Contact us</h2>
      
            <!-- Name input -->
            <mdb-form-control class="mb-4">
              <input mdbInput mdbValidate formControlName="name" required type="text" id="name" class="form-control" />
              <label mdbLabel class="form-label" for="name">Name</label>
              <mdb-error *ngIf="name?.invalid && (name?.dirty || name?.touched)">
                Please provide your name.
              </mdb-error>
              <mdb-success *ngIf="name?.valid && (name?.dirty || name?.touched)">
                Looks good!
              </mdb-success>
            </mdb-form-control>
            <!-- Email input -->
            <mdb-form-control class="mb-4">
              <input mdbInput mdbValidate formControlName="email" required type="email" id="email" class="form-control" />
              <label mdbLabel class="form-label" for="email">Email address</label>
              <mdb-error *ngIf="email?.invalid && (email?.dirty || email?.touched)">
                Please provide your email.
              </mdb-error>
              <mdb-success *ngIf="email?.valid && (email?.dirty || email?.touched)">
                Looks good!
              </mdb-success>
            </mdb-form-control>
            <!-- Subject input -->
            <mdb-form-control class="mb-4">
              <input mdbInput mdbValidate formControlName="subject" required type="text" id="subject" class="form-control" />
              <label mdbLabel class="form-label" for="subject">Subject</label>
              <mdb-error *ngIf="subject?.invalid && (subject?.dirty || subject?.touched)">
                Please provide mail subject.
              </mdb-error>
              <mdb-success *ngIf="subject?.valid && (subject?.dirty || subject?.touched)">
                Looks good!
              </mdb-success>
            </mdb-form-control>
            <!-- Message input -->
            <mdb-form-control class="mb-4">
              <textarea mdbInput mdbValidate formControlName="message" required class="form-control" id="message" rows="4"></textarea>
              <label mdbLabel class="form-label" for="message">Message</label>
              <mdb-error *ngIf="message?.invalid && (message?.dirty || message?.touched)">
                Please provide a message text.
              </mdb-error>
              <mdb-success *ngIf="message?.valid && (message?.dirty || message?.touched)">
                Looks good!
              </mdb-success>
            </mdb-form-control>
            <!-- Mail copy -->
            <div class="form-check">
              <input mdbCheckbox class="form-check-input" type="checkbox" value="" id="copy"/>
              <label class="form-check-label" for="copy">
                Send me copy
              </label>
            </div>
            <!-- Submit button -->
            <button type="button" class="btn btn-primary my-4">Send</button>
          </form>
        </section>
        
        
    
        
            
        import { Component } from '@angular/core';
        import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
        
        @Component({
          selector: 'app-root',
          templateUrl: './app.component.html',
          styleUrls: ['./app.component.scss']
        })
        export class AppComponent {
          validationForm: FormGroup;
        
          constructor() {
            this.validationForm = new FormGroup({
              name: new FormControl(null, Validators.required),
              email: new FormControl(null, Validators.required),
              subject: new FormControl(null, Validators.required),
              message: new FormControl(null, Validators.required),
            });
          }
          
          get name(): AbstractControl {
            return this.validationForm.get('name')!;
          }
        
          get email(): AbstractControl {
            return this.validationForm.get('email')!;
          }
        
          get subject(): AbstractControl {
            return this.validationForm.get('subject')!;
          }
        
          get message(): AbstractControl {
            return this.validationForm.get('message')!;
          }
        }
        
        
        
    

Custom Validation

If you don't want to use MDB default validation and want to create your own functionality instead, you have to change the behavior of the form submission. So first change the existing code in our JavaScript.

        
            
        <section class="border p-4 d-flex justify-content-center mb-4">
          <form (ngSubmit)="onSubmit()" [formGroup]="validationForm" id="form" class="text-center" style="width: 100%; max-width: 300px">
            <h2>Contact us</h2>
      
            <!-- Name input -->
            <mdb-form-control class="mb-4">
              <input mdbInput mdbValidate formControlName="name" required type="text" id="name" class="form-control" />
              <label mdbLabel class="form-label" for="name">Name</label>
              <mdb-error *ngIf="name?.invalid && (name?.dirty || name?.touched)">
                Please provide your name.
              </mdb-error>
              <mdb-success *ngIf="name?.valid && (name?.dirty || name?.touched)">
                Looks good!
              </mdb-success>
            </mdb-form-control>
            <!-- Email input -->
            <mdb-form-control class="mb-4">
              <input mdbInput mdbValidate formControlName="email" required type="email" id="email" class="form-control" />
              <label mdbLabel class="form-label" for="email">Email address</label>
              <mdb-error *ngIf="email?.invalid && (email?.dirty || email?.touched)">
                Please provide your email.
              </mdb-error>
              <mdb-success *ngIf="email?.valid && (email?.dirty || email?.touched)">
                Looks good!
              </mdb-success>
            </mdb-form-control>
            <!-- Subject input -->
            <mdb-form-control class="mb-4">
              <input mdbInput mdbValidate formControlName="subject" required type="text" id="subject" class="form-control" />
              <label mdbLabel class="form-label" for="subject">Subject</label>
              <mdb-error *ngIf="subject?.invalid && (subject?.dirty || subject?.touched)">
                Please provide mail subject.
              </mdb-error>
              <mdb-success *ngIf="subject?.valid && (subject?.dirty || subject?.touched)">
                Looks good!
              </mdb-success>
            </mdb-form-control>
            <!-- Message input -->
            <mdb-form-control class="mb-4">
              <textarea mdbInput mdbValidate formControlName="message" required class="form-control" id="message" rows="4"></textarea>
              <label mdbLabel class="form-label" for="message">Message</label>
              <mdb-error *ngIf="message?.invalid && (message?.dirty || message?.touched)">
                Please provide a message text.
              </mdb-error>
              <mdb-success *ngIf="message?.valid && (message?.dirty || message?.touched)">
                Looks good!
              </mdb-success>
            </mdb-form-control>
            <!-- Mail copy -->
            <div class="form-check">
              <input mdbCheckbox class="form-check-input" type="checkbox" value="" id="copy"/>
              <label class="form-check-label" for="copy">
                Send me copy
              </label>
            </div>
            <!-- Submit button -->
            <button type="submit" class="btn btn-primary my-4">Send</button>
          </form>
        </section>
        
        
    
        
            
        import { Component } from '@angular/core';
        import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
        
        @Component({
          selector: 'app-root',
          templateUrl: './app.component.html',
          styleUrls: ['./app.component.scss']
        })
        export class AppComponent {
          validationForm: FormGroup;
        
          constructor() {
            this.validationForm = new FormGroup({
              name: new FormControl(null, { validators: Validators.required, updateOn: 'submit' }),
              email: new FormControl(null, { validators: Validators.required, updateOn: 'submit' }),
              subject: new FormControl(null, { validators: Validators.required, updateOn: 'submit' }),
              message: new FormControl(null, { validators: Validators.required, updateOn: 'submit' }),
            });
          }
          
          get name(): AbstractControl {
            return this.validationForm.get('name')!;
          }
        
          get email(): AbstractControl {
            return this.validationForm.get('email')!;
          }
        
          get subject(): AbstractControl {
            return this.validationForm.get('subject')!;
          }
        
          get message(): AbstractControl {
            return this.validationForm.get('message')!;
          }
        
          onSubmit(): void {
            this.validationForm.markAllAsTouched();
          }
          
        }
        
        
        
    

Create NodeJS server file

Create a new file called server.js inside your application's root directory.

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

        
            
        <section class="border p-4 d-flex justify-content-center mb-4">
          <form (ngSubmit)="validateForm()" [formGroup]="validationForm" id="form" class="text-center" style="width: 100%; max-width: 300px">
            <h2>Contact us</h2>
      
            <!-- Name input -->
            <mdb-form-control class="mb-4">
              <input mdbInput formControlName="name" type="text" id="name" class="form-control" />
              <label mdbLabel class="form-label" for="name">Name</label>
            </mdb-form-control>
            <!-- Email input -->
            <mdb-form-control class="mb-4">
              <input mdbInput formControlName="email" type="email" id="email" class="form-control" />
              <label mdbLabel class="form-label" for="email">Email address</label>
            </mdb-form-control>
            <!-- Subject input -->
            <mdb-form-control class="mb-4">
              <input mdbInput formControlName="subject" type="text" id="subject" class="form-control" />
              <label mdbLabel class="form-label" for="subject">Subject</label>
            </mdb-form-control>
            <!-- Message input -->
            <mdb-form-control class="mb-4">
              <textarea mdbInput formControlName="message" class="form-control" id="message" rows="4"></textarea>
              <label mdbLabel class="form-label" for="message">Message</label>
            </mdb-form-control>
            <!-- Mail copy -->
            <div class="form-check">
              <input mdbCheckbox class="form-check-input" type="checkbox" value="" id="copy"/>
              <label class="form-check-label" for="copy">
                Send me copy
              </label>
            </div>
            <!-- Submit button -->
            <button type="submit" class="btn btn-primary my-4">Send</button>
            <!-- Status message -->
            <div #status></div>
          </form>
        </section>
        
        
    
        
            
        import { Component, ViewChild, ElementRef } from '@angular/core';
        import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
        
        @Component({
          selector: 'app-root',
          templateUrl: './app.component.html',
          styleUrls: ['./app.component.scss']
        })
        export class AppComponent {
          @ViewChild('status') status: ElementRef;
        
          validationForm: FormGroup;
        
          constructor() {
            this.validationForm = new FormGroup({
              name: new FormControl(),
              email: new FormControl(),
              subject: new FormControl(),
              message: new FormControl(),
              copy: new FormControl(),
            });
          }
          
          get name(): AbstractControl {
            return this.validationForm.get('name')!;
          }
        
          get email(): AbstractControl {
            return this.validationForm.get('email')!;
          }
        
          get subject(): AbstractControl {
            return this.validationForm.get('subject')!;
          }
        
          get message(): AbstractControl {
            return this.validationForm.get('message')!;
          }
        
          get copy(): AbstractControl {
            return this.validationForm.get('copy')!;
          }
          
          checkValidation() {
            let isDataValid = true;
            let statusMessage = "";
        
            if (!this.name.value) {
              statusMessage += '<p class="note note-danger"><strong>Name</strong> cannot be empty</p>';
              isDataValid = false;
            }
        
            if (!this.email.value) {
              statusMessage += '<p class="note note-danger"><strong>Email</strong> cannot be empty</p>';
              isDataValid = false;
            } else {
              const re = /^(([^<>()[]\.,;:s@"]+(.[^<p>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/;
        
              if (!re.test(this.email.value)) {
                statusMessage += '<p class="note note-danger"><strong>Email</strong> is invalid</p>';
                isDataValid = false;
              }
            }
        
            if (!this.subject.value) {
              statusMessage += '<p class="note note-danger"><strong>Subject</strong> cannot be empty</p>';
              isDataValid = false;
            }
        
            if (!this.message.value) {
              statusMessage += '<p class="note note-danger"><strong>Message</strong> cannot be empty</p>';
              isDataValid = false;
            }
        
            return {
              isDataValid,
              statusMessage
            };
          };
        
          validateForm() {
            const { isDataValid, statusMessage } = this.checkValidation();
        
            if (!isDataValid) {
              this.status.nativeElement.innerHTML = statusMessage;
              return;
            }
        
            fetch('/api/contact', {
              method: 'POST',
              body: formData,
              mode: 'cors'
            })
            .then((response) => {
              response.json();
            })
            .then((response) => {
                // handle errors
                if (response.errors) {
                  response.errors.forEach((msg) => {
                    this.status.nativeElement.innerHTML += `<p class="note note-danger">${msg}</p>`
                  });
                  return;
                }
                // If mail was sent successfully, reset all elements with attribute 'name'
                this.validationForm.setValue({
                  name: '',
                  email: '',
                  subject: '',
                  message: '',
                  copy : false,
                });
        
                this.status.nativeElement.innerHTML = `<p class="note note-success">${response.msg}</p>`;
              })
              .catch((err) => {
                this.status.nativeElement.innerHTML += `<p class="note note-danger">${err}</p>`
              })
              .finally(() => {
                setTimeout(() => {
                  this.status.nativeElement.innerHTML = '';
                }, 2000)
              })
            };
          };
        }