Angular Email Validation
Complete guide to email validation in Angular
What You'll Learn
- Install and configure VerifyForge in Angular
- Create custom form validators
- Validate emails with reactive forms
- Build reusable validation services
- Handle async validation
- Best practices for production
Prerequisites
- Angular 16+ installed
- TypeScript knowledge
- VerifyForge API key (get free key)
Installation
npm install @verifyforge/sdk
Quick Start
import { VerifyForge } from '@verifyforge/sdk';
const client = new VerifyForge({ apiKey: 'your_api_key' });
const result = await client.validate('user@example.com');
if (result.data.isValid) {
console.log('✓ Email is valid!');
}
Tutorial 1: Email Validation Service
// src/app/services/email-validation.service.ts
import { Injectable } from '@angular/core';
import { VerifyForge, ValidationResponse } from '@verifyforge/sdk';
import { Observable, from, of } from 'rxjs';
import { catchError, map, shareReplay } from 'rxjs/operators';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class EmailValidationService {
private client: VerifyForge;
private cache = new Map<string, Observable<ValidationResponse>>();
constructor() {
this.client = new VerifyForge({
apiKey: environment.verifyforgeApiKey
});
}
validate(email: string): Observable<ValidationResponse> {
// Check cache
if (this.cache.has(email)) {
return this.cache.get(email)!;
}
// Validate and cache
const validation$ = from(this.client.validate(email)).pipe(
shareReplay(1),
catchError(error => {
console.error('Validation error:', error);
throw error;
})
);
this.cache.set(email, validation$);
// Clear cache after 1 hour
setTimeout(() => this.cache.delete(email), 3600000);
return validation$;
}
validateBulk(emails: string[]): Observable<any> {
return from(this.client.validateBulk(emails));
}
clearCache(): void {
this.cache.clear();
}
}
Tutorial 2: Custom Async Validator
// src/app/validators/email-verifyforge.validator.ts
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { EmailValidationService } from '../services/email-validation.service';
export function emailVerifyForgeValidator(
validationService: EmailValidationService,
options: {
allowDisposable?: boolean;
allowRisky?: boolean;
} = {}
): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if (!control.value) {
return of(null);
}
return of(control.value).pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(email => validationService.validate(email)),
map(result => {
if (!result.data.isValid) {
return { invalidEmail: true };
}
if (!options.allowDisposable && result.data.disposable) {
return { disposableEmail: true };
}
if (!options.allowRisky && result.data.reachability === 'risky') {
return { riskyEmail: true };
}
if (result.data.reachability === 'invalid') {
return { unreachableEmail: true };
}
return null;
}),
catchError(() => of(null)) // Fail gracefully
);
};
}
Tutorial 3: Registration Form Component
// src/app/components/registration-form/registration-form.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { EmailValidationService } from '../../services/email-validation.service';
import { emailVerifyForgeValidator } from '../../validators/email-verifyforge.validator';
@Component({
selector: 'app-registration-form',
templateUrl: './registration-form.component.html',
styleUrls: ['./registration-form.component.css']
})
export class RegistrationFormComponent implements OnInit {
registrationForm!: FormGroup;
isSubmitting = false;
constructor(
private fb: FormBuilder,
private emailValidation: EmailValidationService
) {}
ngOnInit(): void {
this.registrationForm = this.fb.group({
name: ['', [Validators.required]],
email: [
'',
[Validators.required, Validators.email],
[emailVerifyForgeValidator(this.emailValidation, {
allowDisposable: false,
allowRisky: false
})]
],
password: ['', [Validators.required, Validators.minLength(8)]]
});
}
get email() {
return this.registrationForm.get('email');
}
get emailErrors() {
const errors = this.email?.errors;
if (!errors) return null;
if (errors['required']) return 'Email is required';
if (errors['email']) return 'Invalid email format';
if (errors['invalidEmail']) return 'This email is not valid';
if (errors['disposableEmail']) return 'Temporary emails not allowed';
if (errors['riskyEmail']) return 'This email appears risky';
if (errors['unreachableEmail']) return 'This email cannot receive mail';
return null;
}
onSubmit(): void {
if (this.registrationForm.invalid) {
return;
}
this.isSubmitting = true;
// Submit form
const formData = this.registrationForm.value;
console.log('Submitting:', formData);
// Reset after submission
setTimeout(() => {
this.isSubmitting = false;
this.registrationForm.reset();
}, 2000);
}
}
<!-- src/app/components/registration-form/registration-form.component.html -->
<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">Name</label>
<input
id="name"
type="text"
formControlName="name"
class="form-control"
[class.is-invalid]="registrationForm.get('name')?.invalid && registrationForm.get('name')?.touched"
/>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
id="email"
type="email"
formControlName="email"
class="form-control"
[class.is-invalid]="email?.invalid && email?.touched"
[class.is-valid]="email?.valid && email?.touched"
/>
<div *ngIf="email?.pending" class="text-muted">
<small>Validating email...</small>
</div>
<div *ngIf="email?.invalid && email?.touched" class="invalid-feedback">
{{ emailErrors }}
</div>
<div *ngIf="email?.valid && email?.touched" class="valid-feedback">
✓ Email is valid
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input
id="password"
type="password"
formControlName="password"
class="form-control"
[class.is-invalid]="registrationForm.get('password')?.invalid && registrationForm.get('password')?.touched"
/>
</div>
<button
type="submit"
class="btn btn-primary"
[disabled]="registrationForm.invalid || isSubmitting"
>
{{ isSubmitting ? 'Registering...' : 'Register' }}
</button>
</form>
Tutorial 4: Real-time Email Input Component
// src/app/components/email-input/email-input.component.ts
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl } from '@angular/forms';
import { EmailValidationService } from '../../services/email-validation.service';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@Component({
selector: 'app-email-input',
templateUrl: './email-input.component.html',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EmailInputComponent),
multi: true
}]
})
export class EmailInputComponent implements ControlValueAccessor {
emailControl = new FormControl('');
isValidating = false;
validationResult: any = null;
onChange: any = () => {};
onTouched: any = () => {};
constructor(private emailValidation: EmailValidationService) {
this.emailControl.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(email => {
if (!email || !email.includes('@')) {
this.validationResult = null;
return [];
}
this.isValidating = true;
return this.emailValidation.validate(email);
})
).subscribe(result => {
this.isValidating = false;
this.validationResult = result?.data;
});
}
writeValue(value: any): void {
this.emailControl.setValue(value, { emitEvent: false });
}
registerOnChange(fn: any): void {
this.onChange = fn;
this.emailControl.valueChanges.subscribe(fn);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
isDisabled ? this.emailControl.disable() : this.emailControl.enable();
}
}
<!-- src/app/components/email-input/email-input.component.html -->
<div class="email-input-wrapper">
<input
type="email"
[formControl]="emailControl"
placeholder="Enter email address"
class="form-control"
/>
<div *ngIf="isValidating" class="validation-status">
⏳ Validating...
</div>
<div *ngIf="!isValidating && validationResult" class="validation-status">
<span *ngIf="validationResult.isValid" class="text-success">
✓ Valid email
</span>
<span *ngIf="!validationResult.isValid" class="text-danger">
✗ Invalid email
</span>
<small *ngIf="validationResult.suggestion" class="text-muted d-block">
Did you mean {{ validationResult.suggestion }}?
</small>
</div>
</div>
Best Practices
1. Environment Configuration
// src/environments/environment.ts
export const environment = {
production: false,
verifyforgeApiKey: 'your_api_key_here'
};
2. Error Interceptor
// src/app/interceptors/error.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.error?.message?.includes('VerifyForge')) {
console.error('VerifyForge API error:', error);
}
return throwError(() => error);
})
);
}
}
3. Testing
// src/app/services/email-validation.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { EmailValidationService } from './email-validation.service';
describe('EmailValidationService', () => {
let service: EmailValidationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(EmailValidationService);
});
it('should validate email', (done) => {
service.validate('test@example.com').subscribe(result => {
expect(result.data).toBeDefined();
expect(result.data.email).toBe('test@example.com');
done();
});
});
});
