Back to blog
    By

    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

    • ReactiveFormsModule handles validation; HttpClient posts the value object straight to the endpoint as JSON.
    • A JSON post returns 202 Accepted, so subscribe({ next }) fires without a page redirect.
    • Standalone components (Angular 17+) use provideHttpClient() instead of importing HttpClientModule.
    • 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
    • ReactiveFormsModule and HttpClientModule imported

    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

    ValidatorUsage
    Validators.requiredField must not be empty
    Validators.emailMust 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

    1. Sign up at dashboard.formboost.app — free, no credit card
    2. Create a new endpoint and copy the URL
    3. Replace YOUR_ENDPOINT_ID in the component above

    Next steps

    Get your free Formboost endpoint →