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
<!-- 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
<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';