Angular Contact Form - Setup Guide with FormWit
Last updated: March 2026
Building an Angular contact form that sends emails typically means setting up a Node.js backend, configuring an email service, and wiring up API endpoints. With a form backend, you skip all of that. This guide shows how to add a fully functional contact form to any Angular project using reactive forms and FormWit.
How it works
Your Angular component renders a form using reactive forms. When a visitor submits it, the component sends the data to a FormWit endpoint via Angular's HttpClient. FormWit validates the submission, filters spam, stores it in your dashboard, and sends you an email notification. No Express server, no email service, no database.
The flow:
- A visitor fills out the contact form in your Angular app
- The component sends the form data to your FormWit endpoint via
HttpClient - FormWit validates the data, checks for spam, and stores the submission
- You receive an email notification and can view the submission in your dashboard
Set up an Angular contact form
Step 1: Create a FormWit account
Go to app.formwit.com/auth/signup and create a free account. No credit card required. Click Create Form, name it (e.g., "Angular Contact Form"), and copy the endpoint URL. It looks like https://app.formwit.com/api/s/YOUR_FORM_ID.
Step 2: Create the contact form component
Generate a new component using the Angular CLI:
ng generate component contact-form This creates four files. You will edit the TypeScript and HTML files. The component uses Angular's ReactiveFormsModule for form handling and HttpClient to submit data.
contact-form.component.ts
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
ReactiveFormsModule,
FormBuilder,
FormGroup,
Validators,
} from '@angular/forms';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-contact-form',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './contact-form.component.html',
styleUrl: './contact-form.component.css',
})
export class ContactFormComponent {
private fb = inject(FormBuilder);
private http = inject(HttpClient);
readonly FORM_URL = 'https://app.formwit.com/api/s/YOUR_FORM_ID';
status: 'idle' | 'sending' | 'success' | 'error' = 'idle';
contactForm: FormGroup = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
message: ['', Validators.required],
_gotcha: [''],
});
onSubmit(): void {
if (this.contactForm.invalid) {
this.contactForm.markAllAsTouched();
return;
}
this.status = 'sending';
const formData = new FormData();
Object.entries(this.contactForm.value).forEach(([key, value]) => {
formData.append(key, value as string);
});
this.http.post(this.FORM_URL, formData).subscribe({
next: () => {
this.status = 'success';
this.contactForm.reset();
},
error: () => {
this.status = 'error';
},
});
}
} contact-form.component.html
<form [formGroup]="contactForm" (ngSubmit)="onSubmit()">
<div class="field">
<label for="name">Name</label>
<input type="text" id="name" formControlName="name" />
<span
class="error-text"
*ngIf="contactForm.get('name')?.touched && contactForm.get('name')?.invalid"
>
Name is required.
</span>
</div>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" formControlName="email" />
<span
class="error-text"
*ngIf="contactForm.get('email')?.touched && contactForm.get('email')?.invalid"
>
Please enter a valid email address.
</span>
</div>
<div class="field">
<label for="message">Message</label>
<textarea id="message" formControlName="message" rows="5"></textarea>
<span
class="error-text"
*ngIf="contactForm.get('message')?.touched && contactForm.get('message')?.invalid"
>
Message is required.
</span>
</div>
<!-- Honeypot spam protection -->
<input
type="text"
formControlName="_gotcha"
style="display:none"
tabindex="-1"
autocomplete="off"
/>
<button type="submit" [disabled]="status === 'sending'">
{{ status === 'sending' ? 'Sending...' : 'Send Message' }}
</button>
<p class="success-text" *ngIf="status === 'success'">
Message sent successfully!
</p>
<p class="error-text" *ngIf="status === 'error'">
Something went wrong. Please try again.
</p>
</form> Replace YOUR_FORM_ID with the endpoint ID from your FormWit dashboard. The component uses FormData to serialize all fields and sends them via HttpClient.post(). The hidden _gotcha field is a honeypot: bots fill it in, real visitors never see it, and FormWit uses it to filter spam.
Step 3: Provide HttpClient
Angular requires HttpClient to be provided at the application level. If you are using Angular 17+ with standalone components, add provideHttpClient() to your app config:
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
],
}; If you use the older NgModule pattern, import HttpClientModule in your root module instead.
Step 4: Add the component to a page
Use the component in any template. If you have a contact page, import and use it there:
// contact.component.ts
import { Component } from '@angular/core';
import { ContactFormComponent } from '../contact-form/contact-form.component';
@Component({
selector: 'app-contact',
standalone: true,
imports: [ContactFormComponent],
template: `
<h1>Contact Us</h1>
<p>Have a question? Fill out the form below.</p>
<app-contact-form />
`,
})
export class ContactComponent {} Add the route in your router configuration:
// app.routes.ts
import { Routes } from '@angular/router';
import { ContactComponent } from './contact/contact.component';
export const routes: Routes = [
{ path: 'contact', component: ContactComponent },
]; Step 5: Test it
Start the Angular dev server:
ng serve Navigate to /contact, fill in the fields, and click "Send Message." The success message should appear. Check your FormWit dashboard for the submission and your email inbox for the notification.
Template-driven forms alternative
Angular supports two form patterns: reactive forms and template-driven forms. The reactive approach above is recommended for most projects. If you prefer template-driven forms, here is the same component using ngModel:
// contact-form.component.ts (template-driven)
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-contact-form',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './contact-form.component.html',
})
export class ContactFormComponent {
private http = inject(HttpClient);
readonly FORM_URL = 'https://app.formwit.com/api/s/YOUR_FORM_ID';
model = { name: '', email: '', message: '', _gotcha: '' };
status: 'idle' | 'sending' | 'success' | 'error' = 'idle';
onSubmit(): void {
this.status = 'sending';
const formData = new FormData();
Object.entries(this.model).forEach(([key, value]) => {
formData.append(key, value);
});
this.http.post(this.FORM_URL, formData).subscribe({
next: () => {
this.status = 'success';
this.model = { name: '', email: '', message: '', _gotcha: '' };
},
error: () => {
this.status = 'error';
},
});
}
} <!-- Template-driven version -->
<form #contactForm="ngForm" (ngSubmit)="onSubmit()">
<div class="field">
<label for="name">Name</label>
<input type="text" id="name" name="name" [(ngModel)]="model.name" required />
</div>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email" [(ngModel)]="model.email" required />
</div>
<div class="field">
<label for="message">Message</label>
<textarea id="message" name="message" [(ngModel)]="model.message" rows="5" required></textarea>
</div>
<input type="text" name="_gotcha" [(ngModel)]="model._gotcha"
style="display:none" tabindex="-1" autocomplete="off" />
<button type="submit" [disabled]="status === 'sending'">
{{ status === 'sending' ? 'Sending...' : 'Send Message' }}
</button>
<p class="success-text" *ngIf="status === 'success'">Message sent successfully!</p>
<p class="error-text" *ngIf="status === 'error'">Something went wrong. Please try again.</p>
</form> Both approaches send identical data to FormWit. Reactive forms give you more control over validation and are easier to test. Template-driven forms require less TypeScript code and work well for simpler forms.
Form validation details
The reactive forms example includes built-in Angular validators:
Validators.requiredprevents submission with empty fieldsValidators.emailvalidates the email format client-sidemarkAllAsTouched()shows validation errors when the user tries to submit an invalid form
FormWit also validates submissions server-side. If a required field is missing, the API returns an error. Client-side validation improves the user experience by catching issues before the network request.
To add custom validators (like minimum message length), extend the form group:
contactForm: FormGroup = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
message: ['', [Validators.required, Validators.minLength(10)]],
_gotcha: [''],
}); Adding a custom redirect
To redirect visitors to a thank-you page after submission, handle the redirect in your component:
import { Router } from '@angular/router';
private router = inject(Router);
// Inside the subscribe next callback:
this.http.post(this.FORM_URL, formData).subscribe({
next: () => {
this.router.navigate(['/thank-you']);
},
error: () => {
this.status = 'error';
},
}); This uses Angular Router for client-side navigation without a full page reload.
Why not build your own API?
Angular apps often pair with a Node.js or .NET backend. You could build your own /api/contact endpoint. But that means:
- Setting up and maintaining a server process
- Configuring an email service (SendGrid, Resend, Mailgun)
- Managing API keys and environment variables
- Writing validation, sanitization, and error handling
- Building a spam filter from scratch
- No built-in dashboard to view or search submissions
A form backend handles all of this. Your Angular app stays purely client-side, and you can deploy it to any static hosting provider (Firebase Hosting, Vercel, Netlify, AWS S3 + CloudFront).
Summary
Adding a contact form to an Angular app does not require a backend server or email configuration. With FormWit, you create a form endpoint, build a standard Angular component using reactive forms or template-driven forms, and let the service handle spam filtering, data storage, and email notifications. The component works with any Angular version from 14+ and supports both standalone and NgModule architectures.
FormWit's free plan includes unlimited forms, 100 submissions per month, email notifications, and built-in spam protection. Create your free account and add a contact form to your Angular app today.
Related guides: React contact form · Vue contact form · JavaScript contact form · HTML contact form · Spam protection · Contact form templates
Want to skip the setup?
FormWit gives you a form endpoint in 60 seconds. Free plan, no credit card.
Need a form fast?
Build one visually with our free HTML form generator — no coding required.
Try the Form Generator →Add FormWit to your Angular app
Add a contact form to your site in 30 seconds. No backend code required.
Try FormWit Free