Build And Deploy An Angular Form With Netlify Forms And Edge
Creating the frontend, backend, and deployment workflow of an app takes a lot of work. In instances where your app collects only a limited amount of data submissions from its users, building a whole backend may not seem worth the time and effort. An alternative to developing a complete backend is using Netlify Forms. In this tutorial, I’ll explain how you could use an Angular reactive form with Netlify Forms. Since Netlify Forms only work when deployed on Netlify, I’ll also illustrate how to deploy your app on Netlify Edge.
The Toolkit
An Angular reactive form is a form that has a structured data model created explicitly within a component class using the ReactiveFormsModule providers. A form model is created for each input element within the form view. This form model is an instance of the FormControl class and it keeps track of the value of the form element. The form model is immutable because whenever a change is made to the model the FormControl instance returns a new data model instead of updating the old model. Its immutability makes change detection more efficient and allows data alteration with observable operators. Since form input elements are directly connected to their form models, updates between them are synchronous and do not rely on UI rendering.
Netlify is a platform that allows you to build, deploy, and host sites built with various technologies. Sites built with Angular can be hosted on Netlify. Netlify additionally provides a host of tools that simplify, automate, and augment builds and deployments of these sites. We’re going to use two of its products in this tutorial: Netlify Edge and Netlify Forms.
As described earlier, Netlify Forms is a form handling feature that receives submissions from HTML forms automatically. It does not require any submission processing configuration, like creating APIs, scripts, etc. This feature only works with forms in sites deployed on Netlify. It is enabled by default, further reducing the configuration needed to set up the form submissions. Submission handling is set up during deployment where a site’s HTML files are parsed by Netlify’s build bots.
Netlify Edge is a global application delivery network on which sites and applications are published. It provides features like A/B testing, rollbacks, staging, and phased rollouts. All deployments on Netlify Edge are atomic, meaning a site is only live when all files have been uploaded/updated and changes to the site are ready. Once a site is deployed, it is assigned a subdomain on netlify.app when deployed to production. Netlify Edge also supports preview and branch deployments (staging, development, etc.).
Netlify Forms submission-handling works because build bots parse HTML forms on a site during deployment. Client-side Javascript rendered forms like those in compiled Angular sites won’t be found by these bots. So the normal set up for Netlify Forms won’t work with Angular Forms.
However, there is a work-around to this. To get it to receive submissions, a hidden plain HTML form is added to the index.html
file. This form works with the build bots. When submitting the Angular Form, a post request is made to this hidden form which is then captured by Netlify Forms.
In this article, we will create a reactive form. We’ll also develop a service to make a post request to the hidden HTML form. Lastly, we will deploy the app to Netlify Edge.
Example
To illustrate how to build the app, we will take an example of a feedback form common on many websites. We will use this form to collect comments/complaints, questions, and suggestions from users of the site along with their name and email. We shall also use it to collect their rating of the site.
Requirements
To follow along with this tutorial, you will need a Netlify account and the Angular CLI installed. If you do not have the CLI, you can install it using npm.
npm install -g @angular/cli
If you’ve not signed up for a Netlify account yet, you can create one here. Netlify offers sign-up through Github, Gitlab, Bitbucket, or Email. Depending on what deployment method you choose to go with, they may be other requirements. They will be stated under each deployment method.
Setting Up The App
To start, we will create the app and call it feedback
. When creating it, add routing to it when asked in the prompts.
ng new feedback
Next, we’ll generate three components: a feedback form, a successful submission message page, and a 404 page. Netlify Forms allow you to navigate to a page upon successful form entry submission. That’s what we’ll use the SuccessComponent
for.
ng g c feedback
ng g c success
ng g c page-not-found
After generating the components, we’ll add the routes to each page in the AppRoutingModule
within the app-routing.module.ts
file.
const routes: Routes = [
{ path:'', component: FeedbackComponent },
{ path: 'success', component: SuccessComponent },
{ path: '**', component: PageNotFoundComponent }
];
We’ll use the FormBuilder
service to create our reactive form. This is because it is more convenient and less repetitive than using basic form controls. To have access to it, we’ll need to register the ReactiveFormsModule
in the app.module.ts
file.
Since we will be making a post request to the hidden HTML form, we also have to register the HttpClientModule
.
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
// other imports
ReactiveFormsModule,
HttpClientModule
]
})
export class AppModule { }
Proceed to change the contents of app.component.html
to just have the router outlet.
<router-outlet></router-outlet>
The different pages will share some styling. So add the styling below to styles.css.
html, body {
height: 100%;
width: 100%;
display: flex;
align-items: flex-start;
justify-content: center;
}
h1 {
margin: 0;
text-align: center;
}
h1, p, label {
font-family: Arial, Helvetica, sans-serif;
}
p {
max-width: 25rem;
}
#container {
border: none;
padding: .4rem;
border-radius: 0;
flex-direction: column;
display: flex;
}
hr {
width: 80%;
}
button {
color: white;
background-color: black;
font-size: large;
padding: .5rem;
border-radius: .5rem;
margin-top: 1rem;
}
@media screen and (min-height: 700px) {
html, body {
align-items: center;
justify-content: center;
}
}
@media screen and (min-width: 480px) {
#container {
border: .1rem solid lightgray;
padding: 2rem;
border-radius: .5rem;
}
html, body {
align-items: center;
justify-content: center;
}
}
Create The Reactive Form
In our FeedbackComponent
class, we will begin by importing the FormBuilder
service which we’ll use to create the form. We’ll also import the Validators
class for form input validation.
import { FormBuilder, Validators } from '@angular/forms';
We will then inject the FormBuilder
service by adding it to the FeedbackComponent
constructor.
constructor(private fb: FormBuilder) { }
Next, we’ll define the form model using the group
method of the injected FormBuilder
service. We’ll also add an errorMsg
property to hold any errors we may encounter when submitting the form input. Also included is a closeError
method that will close the error alert that displays on the form.
Each control in the form model will be verified using validators from the Validators
class. If any of the inputs fail validation, the form will be invalid and submission will be disabled. You can choose to add multiple validators to a form control like in the case of the email
control.
export class FeedbackComponent {
feedbackForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.email, Validators.required]],
type: ['', Validators.required],
description: ['', Validators.required],
rating: [0, Validators.min(1)]
});
errorMsg = '';
closeError() {
this.errorMsg = '';
}
// ...
}
In the component’s template (feedback.component.html
), we shall add this.
<div id="container">
<div class="error" [class.hidden]="errorMsg.length == 0">
<p>{{errorMsg}}</p>
<span (click)="closeError()" class="close">✖︎</span>
</div>
<h1>Feedback Form</h1>
<hr>
<p>We’d like your feedback to improve our website.</p>
<form [formGroup]="feedbackForm" name="feedbackForm" (ngSubmit)="onSubmit()">
<div id="options">
<p class="radioOption">
<input formControlName="type" type="radio" id="suggestion" name="type" value="suggestion">
<label for="suggestion">Suggestion</label><br>
</p>
<p class="radioOption">
<input formControlName="type" type="radio" id="comment" name="type" value="comment">
<label for="comment">Comment</label><br>
</p>
<p class="radioOption">
<input formControlName="type" type="radio" id="question" name="type" value="question">
<label for="question">Question</label><br>
</p>
</div>
<div class="inputContainer">
<label>Description:</label>
<textarea rows="6" formControlName="description"></textarea>
</div>
<div class="inputContainer">
<div id="ratingLabel">
<label>How would you rate our site?</label>
<label id="ratingValue">{{feedbackForm.value?.rating}}</label>
</div>
<input formControlName="rating" type="range" name="rating" max="5">
</div>
<div class="inputContainer">
<label>Name:</label>
<div class="nameInput">
<input formControlName="firstName" type="text" name="firstName" placeholder="First">
<input formControlName="lastName" type="text" name="lastName" placeholder="Last">
</div>
</div>
<div class="inputContainer">
<label>Email:</label>
<input formControlName="email" type="email" name="email">
</div>
<div class="inputContainer">
<button type="submit" [disabled]="feedbackForm.invalid">Submit Feedback</button>
</div>
</form>
</div>
Note that the form element should have the [formGroup]="feedbackForm"
attribute corresponding to the model we just created. Also, each of the input elements should have a formControlName=""
attribute corresponding to its counterpart form control in the model.
To style the form, add this tofeedback.component.css
.
#options {
display: flex;
flex-direction: column;
}
#options label {
margin: 0 0 0 .2rem;
}
.radioOption {
margin: 0 0 .2rem 0;
}
.inputContainer {
display: flex;
flex-direction: column;
margin: .5rem 0 .5rem 0;
}
label {
margin: .5rem 0 .5rem 0;
}
.nameInput {
display: flex;
flex-direction: column;
}
button:disabled {
cursor: not-allowed;
pointer-events: all;
background-color: slategrey;
}
#ratingLabel {
display: flex;
justify-content: space-between;
margin: .5rem 0 .5rem 0;
}
#ratingValue {
font-weight: bolder;
font-size: large;
border: .1rem solid lightgray;
padding: .4rem .6rem .1rem .6rem;
margin: 0;
vertical-align: middle;
border-radius: .3rem;
}
.error {
color: darkred;
background-color: lightsalmon;
border: .1rem solid crimson;
border-radius: .3rem;
padding: .5rem;
text-align: center;
margin: 0 0 1rem 0;
display: flex;
width: inherit;
}
.error p {
margin: 0;
flex-grow: 1;
}
textarea, input {
margin: .1rem;
font-family: Arial, Helvetica, sans-serif;
padding: 5px;
font-size: medium;
font-weight: lighter;
}
.close {
cursor: default;
}
.hidden {
display: none;
}
@media screen and (min-width: 480px) {
#options {
flex-direction: row;
justify-content: space-around;
}
.nameInput {
flex-direction: row;
justify-content: space-between;
}
}
This is what the form will look like:
Adding A Hidden HTML Form
As stated earlier, we need to add a hidden HTML form that the Netlify Forms build bots can parse. Submissions will then be sent from our reactive form to the hidden HTML form. The HTML form is put in the index.html file.
This form should have the same name as the reactive form. Additionally, it should contain three other attributes: netlify
, netlify-honeypot
, and hidden
. The bots look for any forms that have the netlify
attribute so that Netlify can process inputs from them. The netlify-honeypot
attribute is added to prevent captchas from being shown when a submission is made and enables extra spam protection.
<!doctype html>
<html lang="en">
<!-- Head -->
<body>
<form name="feedbackForm" netlify netlify-honeypot="bot-field" hidden>
<input type="text" name="firstName"/>
<input type="text" name="lastName"/>
<input type="text" name="email"/>
<input type="text" name="feedbackType"/>
<input type="text" name="description"/>
<input type="text" name="rating"/>
</form>
<app-root></app-root>
</body>
</html>
It’s important to note that since you can’t set the value of file
input elements, you can’t upload a file using this method.
Making A Post Request To The Hidden Form
To send a submission from the reactive form to the HTML form, we’ll make a post request containing the submission to index.html
. The operation will be performed in the onSubmit
method of the FeedbackComponent
.
However, before we can do that, we need to create two things: a Feedback
interface and a NetlifyFormsService
. Let’s start with the interface.
touch src/app/feedback/feedback.ts
The contents of this file will be:
export interface Feedback {
firstName: string;
lastName: string;
email: string;
type: string;
description: string;
rating: number;
}
The NetlifyFormsService
will contain a public method to submit a feedback entry, a private method to submit a generic entry, and another private one to handle any errors. You could add other public methods for additional forms.
To generate it, run the following:
ng g s netlify-forms/netlify-forms
The submitEntry
method returns an Observable<string>
because Netlify sends a HTML page with a success alert once we post data to the form. This is the service:
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { Feedback } from '../feedback/feedback';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class NetlifyFormsService {
constructor(private http: HttpClient) { }
submitFeedback(fbEntry: Feedback): Observable {
const entry = new HttpParams({ fromObject: {
'form-name': 'feedbackForm',
...fbEntry,
'rating': fbEntry.rating.toString(),
}});
return this.submitEntry(entry);
}
private submitEntry(entry: HttpParams): Observable {
return this.http.post(
'/',
entry.toString(),
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'text'
}
).pipe(catchError(this.handleError));
}
private handleError(err: HttpErrorResponse) {
let errMsg = '';
if (err.error instanceof ErrorEvent) {
errMsg = `A client-side error occurred: ${err.error.message}`;
} else {
errMsg = `A server-side error occurred. Code: ${err.status}. Message: ${err.message}`;
}
return throwError(errMsg);
}
}
We’ll send the form submission as HttpParams
. A header for the ContentType
should be included with the value application/x-www-form-urlencoded
. The responseType
option is specified as text
because if successful, posting to the hidden form will return an HTML page containing a generic success message from Netlify. If you do not include this option, you will get an error because the response will be parsed as JSON
. Below is a screenshot of the generic Netlify success message.
In the FeedbackComponent
class, we shall import the NetlifyFormsService
and Router
. We’ll submit the form entry using the NetlifyFormsService.submitEntry
method. If the submission is successful, we will redirect to the successful submission page and reset the form. We’ll use the Router
service for the redirection. If unsuccessful, the errorMsg
property will be assigned the error message and be displayed on the form.
import { Router } from '@angular/router';
import { NetlifyFormsService } from '../netlify-forms/netlify-forms.service';
After that, inject both the NetlifyFormsService
and Router
in the constructor.
constructor(
private fb: FormBuilder,
private router: Router,
private netlifyForms: NetlifyFormsService
) {}
Lastly, call the NetlifyFormsService.submitEntry
method in FeedbackComponent.onSubmit
.
onSubmit() {
this.netlifyForms.submitFeedbackEntry(this.feedbackForm.value).subscribe(
() => {
this.feedbackForm.reset();
this.router.navigateByUrl('/success');
},
err => {
this.errorMsg = err;
}
);
}
Create A Successful Submission Page
When a user completes a submission, Netlify returns a generic success message shown in the last screenshot of the previous section. However, you can link back to your own custom success message page. You do this by adding the action
attribute to the hidden HTML form. Its value is the relative path to your custom success page. This path must start with /
and be relative to your root site.
Setting a custom success page, however, does not seem to work when using a hidden HTML form. If the post request to the hidden HTML form is successful, it returns the generic Netlify success message as an HTML page. It does not redirect even when an action
attribute is specified. So instead we shall navigate to the success message page after a submission using the Router
service.
First, let’s add content to the SuccessComponent
we generated earlier. In success.component.html
, add:
<div id="container">
<h1>Thank you!</h1>
<hr>
<p>Your feedback submission was successful.</p>
<p>Thank you for sharing your thoughts with us!</p>
<button routerLink="/">Give More Feedback</button>
</div>
To style the page, add this to success.component.css
:
p {
margin: .2rem 0 0 0;
text-align: center;
}
This is what the page looks like:
In the FeedbackComponent
class, we already added the Router
service as an import and injected it into the constructor. In its onSubmit
method, after the request is successful and the form has reset, we navigate to the successful submission page, /success
. We use the navigateByUrl
method of the router to do that.
Creating The 404 Page
The 404 page may not be necessary but is a nice to have. The contents of page-not-found.component.html
would be:
<div id="container">
<h1>Page Not Found!</h1>
<hr>
<p>Sorry! The page does not exist.</p>
<button routerLink="/">Go to Home</button>
</div>
To style it, add this to page-not-found.component.css
:
p {
text-align: center;
}
This is what the 404 page will look like.
Fix Routing Before Deployment
Since we’re using the Router
service, all our routing is done on the client. If a link to a page in our app is pasted in the address bar (deep link) or there is a page refresh, that request we’ll be sent to our server. The server does not contain any of our routes because they were configured in the frontend, in our app. We’ll receive a 404 status in these instances.
To fix this, we need to tell the Netlify server to redirect all requests to our index.html
page. This way our Angular router can handle them. If you’re interested, you can read more about this phenomenon here and here.
We’ll start by creating a _redirects
file in our src folder. The _redirects
file is a plain text file that specifies redirect and rewrite rules for the Netlify site. It should reside in the site publish site directory (dist/<app_name>
). We’ll place it in the src
folder and specify it as an asset in the angular.json
file. When the app is compiled, it will be placed in dist/<app_name>
.
touch src/_redirects
This file will contain the rule below. It indicates that all requests to the server should be redirected to index.html
. We also add a HTTP status code option at the end to indicate that these redirects should return a 200
status. By default, a 301
status is returned.
/* /index.html 200
The last thing we have to do is add the below option in our angular.json
und er projects > {your_project_name} > architect > options > assets
. Include it in the assets
array:
{
"glob": "_redirects",
"input": "src",
"output": "/"
}
Preview Your App Locally
Before you can deploy the feedback app, it’s best to preview it. This allows you to make sure your site works as you had intended it. You may unearth issues resulting from the build process like broken paths to resources among other things. First, you’ll have to build your app. We’ll then serve the compiled version using a server. We’ll use lite-server which is a lightweight live-reload server for web apps.
Note: Since the app is not deployed on Netlify just yet, you’ll get a 404 error when you attempt to make the post request. This is because Netlify Forms only work on deployed apps. You’ll see an error on the form as shown in the screenshot below, however, it will work once you’ve deployed it.
- To begin, install lite-server:
npm install lite-server --save-dev
- Next, within your app’s workspace directory, build your app. To make sure builds are run every time your files change, pass the
--watch
flag to it. Once the app is compiled, the results are written to the `dist/` output directory. If you are using a version control system, make sure to not check in the dist
folder because it is generated and is only for preview purposes.ng build --watch
- To serve the compiled site, run the
lite-server
against the build output directory.lite-server --baseDir="dist/<app name>"
The site is now served at localhost:3000
. Check it out on your browser and make sure it works as expected before you begin its deployment.
Deployment
There are multiple ways you can deploy your Angular project onto Netlify Edge. We shall cover three here:
1. Using netlify-builder
netlify-builder facilitates the deployment of Angular apps through the Angular CLI. To use this method, your app needs to have been created using Angular CLI v8.3.0 or higher.
- From the Sites tab of your Netlify dashboard, create a new project. Since we won't be using Git to create a project, drag any empty folder to the dotted-border area marked "Drag and drop your site folder here". This will automatically create a project with a random name. You can change this name under the site’s domain settings later if you wish.
This is what you should see once your project has been created.
You can get a personal access token in your user settings. At User Settings > Applications > Personal access tokens, click the New Access Token button. When prompted, enter the description of your token, then click the Generate Token button. Copy your token. For persistence’s sake, you can store these values in a .env
file within your project but do not check this file in if you are using a version control system.
netlify-builder
to your project using ng add
.ng add @netlify-builder/deploy
Once it’s done installing, you will be prompted to add the API ID and personal access token.
It’s optional to add these here. You could ignore this prompt because they will be added to your angular.json
file which is usually checked in if you use a version control system. It’s not safe to store this kind of sensitive information on code repos. If you are not checking this file in, you could just input your API ID and personal access token. The entry below will be modified in your angular.json
file under the architect
settings.
"deploy": {
"builder": "@netlify-builder/deploy:deploy",
"options": {
"outputPath": "dist/<app name>",
"netlifyToken": "",
"siteId": ""
}
}
NETLIFY_TOKEN=<access token> NETLIFY_API_ID=<api id> ng deploy
Alternatively, you could put this in a script and run it when you need to deploy your app.
# To create the script
touch deploy.sh && echo "NETLIFY_TOKEN=<access token> NETLIFY_API_ID=<api id> ng deploy" >> deploy.sh && chmod +x deploy.sh
# To deploy
./deploy.sh
This is the output you should see once you run this command:
2. Using Git And The Netlify Web UI
If your Angular app’s code is hosted on either Github, Bitbucket, or Gitlab, you can host the project using Netlify’s web UI.
- From the Sites tab on your Netlify dashboard, click the “
New site from Git” button. - Connect to a code repository service. Pick the service where your app code is hosted. You’ll be prompted to authorize Netlify to view your repositories. This will differ from service to service.
- Pick your code repository.
- Next, you’ll specify the deployments and build settings. In this case, select the branch you’d like to deploy from, specify the build command as
ng deploy --prod
and the publish directory as `dist/`. - Click the Deploy Site button and you’re done.
3. Using The Netlify CLI Tool
- To start, install the Netlify CLI tool as follows:
If the installation is successful, you should see these results on your terminal:npm install netlify-cli -g
- Next, log in to Netlify by running:
When you run this command, it will navigate to a browser window where you will be prompted to authorize the Netlify CLI. Click thenetlify login
Authorize
button. You can then proceed to close the tab once authorization is granted. - To create a new Netlify project, run the following on your terminal:
You will be prompted to either connect your Angular app to an existing Netlify project or create a new one. Choose the Create & configure a new site option.Next, select your team and a name for the site you would like to deploy. Once the project has been created, the CLI tool will list site details for your project.netlify init
After which the CLI tool will prompt you to connect your Netlify account to a Git hosting provider to configure webhooks and deploy keys. You cannot opt-out of this. Pick an option to login in then authorize Netlify.
Next, you’ll be asked to enter a build command. Use:
Afterward, you’ll be asked to provide a directory to deploy. Enter `dist/ng build --prod
` with your app’s name.
At the end of that, the command will complete and display this output. - To deploy the app, run:
Using thenetlify deploy --prod
--prod
flag ensures that the build is deployed to production. If you omit this flag, thenetlify deploy
command will deploy your build to a unique draft URL that is used for testing and previewing. Once the deployment is complete, you should see this output:
Viewing Form Submissions
Form submissions can be viewed on the Netlify dashboard under the Forms tab of your site. You can find it at app.netlify.com/sites/<your_site_name>/forms
. On this page, all your active forms will be listed. The name attribute that you put down in the hidden form element is the name of the form on the dashboard.
Once you select a form, all the submissions for that form will be listed. You can choose to download all the entries as a CSV file, mark them as spam, or delete them.
Conclusion
Netlify Forms allow you to collect form submission from your app without having to create or configure a backend to do it. This can be useful especially in apps that only need to collect a limited amount of data like contact information, customer feedback, event sign-ups, and so on.
Pairing Angular reactive forms with Netlify forms allow you to structure your data model. Angular reactive forms have the added benefit of having their data model and form elements being in sync with each other. They do not rely on UI rendering.
Although Netlify Forms only work when deployed on Netlify Edge, the hosting platform is pretty robust, provides useful features like A/B testing, and automates app builds and deployments.
You can continue reading more about using Netlify with your forms over here.
Further Reading
- Material Design Text Fields Are Badly Designed
- Event Calendars For Web Made Easy With These Commercial Options
- How To Create A Card Matching Game Using Angular And RxJS
- Things I Wish I Had Known About Angular When I Started