Angular Contact Form with Reactive Forms
Build a contact form in Angular with ReactiveFormsModule and HttpClient. Includes validation, loading states, error handling and submission to Formboost.
Angular's ReactiveFormsModule and HttpClient are the right tools for building robust contact forms. This guide shows how to build a complete contact form component in Angular — with validation, loading state, error handling, and submission to an external endpoint — no backend server required.
Key takeaways
ReactiveFormsModulehandles validation;HttpClientposts the value object straight to the endpoint as JSON.- A JSON post returns
202 Accepted, sosubscribe({ next })fires without a page redirect.- Standalone components (Angular 17+) use
provideHttpClient()instead of importingHttpClientModule.- Nothing here needs a backend, an API route, or a proxy — the browser posts cross-origin directly.
Reactive forms are Angular's model-driven approach: the form state lives in a FormGroup in your component class rather than in the template, which makes validation and submission explicit and testable.
Prerequisites
- Angular v15+ project
ReactiveFormsModuleandHttpClientModuleimported
Setup
Add HttpClientModule to your app module (or use provideHttpClient() in standalone apps):
1// app.module.ts
2import { HttpClientModule } from '@angular/common/http';
3
4@NgModule({
5 imports: [
6 BrowserModule,
7 ReactiveFormsModule,
8 HttpClientModule,
9 ],
10})
11export class AppModule {}For standalone components (Angular 17+):
1// main.ts
2import { provideHttpClient } from '@angular/common/http';
3
4bootstrapApplication(AppComponent, {
5 providers: [provideHttpClient()],
6});The Contact Form Component
1// contact-form.component.ts
2import { Component } from '@angular/core';
3import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
4import { HttpClient } from '@angular/common/http';
5import { CommonModule } from '@angular/common';
6
7@Component({
8 selector: 'app-contact-form',
9 standalone: true,
10 imports: [ReactiveFormsModule, CommonModule],
11 template: `
12 <div *ngIf="submitted; else formTemplate">
13 <p>Thanks! We'll be in touch soon.</p>
14 </div>
15
16 <ng-template #formTemplate>
17 <form [formGroup]="form" (ngSubmit)="onSubmit()">
18 <div>
19 <label for="name">Name</label>
20 <input id="name" type="text" formControlName="name" />
21 <span *ngIf="f['name'].touched && f['name'].errors?.['required']">
22 Name is required
23 </span>
24 </div>
25
26 <div>
27 <label for="email">Email</label>
28 <input id="email" type="email" formControlName="email" />
29 <span *ngIf="f['email'].touched && f['email'].errors?.['required']">
30 Email is required
31 </span>
32 <span *ngIf="f['email'].touched && f['email'].errors?.['email']">
33 Enter a valid email address
34 </span>
35 </div>
36
37 <div>
38 <label for="message">Message</label>
39 <textarea id="message" formControlName="message" rows="5"></textarea>
40 </div>
41
42 <p *ngIf="error" style="color: red">{{ error }}</p>
43
44 <button type="submit" [disabled]="submitting || form.invalid">
45 {{ submitting ? 'Sending…' : 'Send Message' }}
46 </button>
47 </form>
48 </ng-template>
49 `,
50})
51export class ContactFormComponent {
52 form: FormGroup;
53 submitting = false;
54 submitted = false;
55 error = '';
56
57 constructor(private fb: FormBuilder, private http: HttpClient) {
58 this.form = this.fb.group({
59 name: ['', Validators.required],
60 email: ['', [Validators.required, Validators.email]],
61 message: [''],
62 });
63 }
64
65 get f() {
66 return this.form.controls;
67 }
68
69 onSubmit() {
70 if (this.form.invalid) return;
71
72 this.submitting = true;
73 this.error = '';
74
75 this.http
76 .post('https://formboost.app/f/YOUR_ENDPOINT_ID', this.form.value)
77 .subscribe({
78 next: () => {
79 this.submitted = true;
80 this.submitting = false;
81 },
82 error: () => {
83 this.error = 'Something went wrong. Please try again.';
84 this.submitting = false;
85 },
86 });
87 }
88}Replace YOUR_ENDPOINT_ID with your Formboost endpoint ID.
Template-Driven Alternative
If you prefer template-driven forms:
1<!-- contact-form.component.html -->
2<form #contactForm="ngForm" (ngSubmit)="onSubmit(contactForm)">
3 <div>
4 <label for="name">Name</label>
5 <input id="name" name="name" ngModel required #name="ngModel" />
6 <span *ngIf="name.touched && name.invalid">Name is required</span>
7 </div>
8
9 <div>
10 <label for="email">Email</label>
11 <input id="email" name="email" type="email" ngModel required email #email="ngModel" />
12 <span *ngIf="email.touched && email.errors?.['required']">Email is required</span>
13 <span *ngIf="email.touched && email.errors?.['email']">Invalid email</span>
14 </div>
15
16 <div>
17 <label for="message">Message</label>
18 <textarea id="message" name="message" ngModel rows="5"></textarea>
19 </div>
20
21 <button type="submit" [disabled]="contactForm.invalid">Send</button>
22</form>1onSubmit(form: NgForm) {
2 this.http
3 .post('https://formboost.app/f/YOUR_ENDPOINT_ID', form.value)
4 .subscribe({
5 next: () => { this.submitted = true; },
6 error: () => { this.error = 'Something went wrong.'; },
7 });
8}Validation Summary
| Validator | Usage |
|---|---|
Validators.required | Field must not be empty |
Validators.email | Must be a valid email format |
Validators.minLength(n) | Minimum character count |
Validators.maxLength(n) | Maximum character count |
Validators.pattern(regex) | Custom regex validation |
Getting Your Endpoint
- Sign up at dashboard.formboost.app — free, no credit card
- Create a new endpoint and copy the URL
- Replace
YOUR_ENDPOINT_IDin the component above