Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to build a signup page in Angular

A signup page in Angular requires a reactive form with custom validators for password confirmation, client-side validation before submission, and an HTTP call to register the user on the backend. As the creator of CoreUI with Angular development experience since 2014, I designed the registration components in CoreUI Angular templates that handle the complete sign-up flow including error handling and success redirects. The most important custom validation pattern is the password confirmation check — Angular’s built-in validators don’t cover cross-field validation, so you must write a custom group validator. This validator compares two fields and marks the confirmation field as invalid if they don’t match.

Create a signup form with custom password match validation.

// signup.component.ts
import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { ReactiveFormsModule, FormBuilder, Validators, AbstractControl, ValidationErrors } from '@angular/forms'
import { Router, RouterLink } from '@angular/router'
import { CardModule, FormModule, ButtonModule, GridModule } from '@coreui/angular'
import { AuthService } from '../auth/auth.service'

// Custom validator: checks password === confirmPassword
function passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
  const password = group.get('password')?.value
  const confirm = group.get('confirmPassword')?.value
  return password === confirm ? null : { passwordMismatch: true }
}

@Component({
  selector: 'app-signup',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule, CardModule, FormModule, ButtonModule, GridModule, RouterLink],
  template: `
    <div class="min-vh-100 d-flex align-items-center">
      <c-container>
        <c-row class="justify-content-center">
          <c-col md="7" lg="6">
            <c-card class="p-4">
              <c-card-body>
                <h1 class="h4 mb-4">Create Account</h1>

                <form [formGroup]="form" (ngSubmit)="submit()">
                  <c-row>
                    <c-col sm="6">
                      <div class="mb-3">
                        <label cLabel for="firstName">First Name</label>
                        <input cFormControl id="firstName" formControlName="firstName"
                          [class.is-invalid]="isInvalid('firstName')" />
                        <c-form-feedback *ngIf="isInvalid('firstName')">Required</c-form-feedback>
                      </div>
                    </c-col>
                    <c-col sm="6">
                      <div class="mb-3">
                        <label cLabel for="lastName">Last Name</label>
                        <input cFormControl id="lastName" formControlName="lastName"
                          [class.is-invalid]="isInvalid('lastName')" />
                        <c-form-feedback *ngIf="isInvalid('lastName')">Required</c-form-feedback>
                      </div>
                    </c-col>
                  </c-row>

                  <div class="mb-3">
                    <label cLabel for="email">Email</label>
                    <input cFormControl id="email" type="email" formControlName="email"
                      [class.is-invalid]="isInvalid('email')" />
                    <c-form-feedback *ngIf="isInvalid('email')">Valid email required</c-form-feedback>
                  </div>

                  <div class="mb-3">
                    <label cLabel for="password">Password</label>
                    <input cFormControl id="password" type="password" formControlName="password"
                      [class.is-invalid]="isInvalid('password')" />
                    <c-form-feedback *ngIf="isInvalid('password')">
                      At least 8 characters
                    </c-form-feedback>
                  </div>

                  <div class="mb-4">
                    <label cLabel for="confirmPassword">Confirm Password</label>
                    <input cFormControl id="confirmPassword" type="password" formControlName="confirmPassword"
                      [class.is-invalid]="showPasswordMismatch" />
                    <c-form-feedback *ngIf="showPasswordMismatch">
                      Passwords do not match
                    </c-form-feedback>
                  </div>

                  <p *ngIf="error" class="text-danger">{{ error }}</p>

                  <button cButton color="success" type="submit" class="w-100" [disabled]="loading">
                    {{ loading ? 'Creating account...' : 'Create Account' }}
                  </button>
                </form>

                <div class="mt-3 text-center">
                  Already have an account? <a routerLink="/login">Sign in</a>
                </div>
              </c-card-body>
            </c-card>
          </c-col>
        </c-row>
      </c-container>
    </div>
  `
})
export class SignupComponent {
  loading = false
  error = ''

  form = this.fb.group({
    firstName: ['', Validators.required],
    lastName: ['', Validators.required],
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]],
    confirmPassword: ['', Validators.required]
  }, { validators: passwordMatchValidator })

  constructor(
    private fb: FormBuilder,
    private auth: AuthService,
    private router: Router
  ) {}

  get showPasswordMismatch(): boolean {
    const confirm = this.form.get('confirmPassword')
    return !!(confirm?.touched && this.form.errors?.['passwordMismatch'])
  }

  isInvalid(field: string): boolean {
    const c = this.form.get(field)
    return !!(c?.invalid && c.touched)
  }

  async submit(): Promise<void> {
    if (this.form.invalid) {
      this.form.markAllAsTouched()
      return
    }

    this.loading = true
    this.error = ''

    try {
      await this.auth.register(this.form.value)
      this.router.navigate(['/dashboard'])
    } catch (err: any) {
      this.error = err.message ?? 'Registration failed. Please try again.'
    } finally {
      this.loading = false
    }
  }
}

The passwordMatchValidator is passed as a group validator, not a field validator. This gives it access to both password and confirmPassword. The showPasswordMismatch getter checks the group-level error only after the confirmation field has been touched.

Best Practice Note

This is the registration form pattern used in CoreUI Angular Admin Template — the complete auth flow is included out of the box. For production, add email verification: create the user with verified: false, send a verification email with a token, and set verified: true when the user clicks the link. See how to build a login page in Angular for the companion login flow.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
JavaScript Template Literals: Complete Developer Guide
JavaScript Template Literals: Complete Developer Guide

How to replace all occurrences of a string in JavaScript?
How to replace all occurrences of a string in JavaScript?

How to Open All Links in New Tab Using JavaScript
How to Open All Links in New Tab Using JavaScript

How to get element ID in JavaScript
How to get element ID in JavaScript

Answers by CoreUI Core Team