Angular Form

Angular Form Validation

Table of Contents

  1. Angular Forms Overview
  2. Key concept of Demonstrated
  3. Potential Interview Question
  4. Route concupt
  5. Best practices
  6. Common Pitfalls

1. Angular Forms Overview

Template-Driven Forms

Key Features

  • Use FormsModule and ngModel directive
  • Two-way data binding with [(ngModel)]
  • Validation attributes in HTML (required, minLength)
  • From state traking : touchedvalidinvaliddirty

Example:

<input [(ngModel)]="username" required minLength="3">

When to Use:

  • Simple forms with basic validation
  • Quick prototypes
  • Forms that mirror data model directly

Reactive Forms

Key Feature:

  • Use ReactiveFormsModuleFormGroupFormCntrol
  • Programtic control and validation
  • Custom validators: noSpeceValidetors and abusiveWordValidetors
  • Better testability and scalability

Example:

this.login = new FormGroup({
    name: new FormCntrol('', [Validators.required, noSpaceValidators]),
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormCntrol('', [Validators.required, Validators.minLength(6)])
});

When to Use:

  • Complex forms with dynamic validation
  • Cross-field validation
  • Better control over from state
  • Easier unit testing

Form Arrays

Key features:

  • Dynamic form controls (add/ remove skills)
  • Use FormArray for collections
  • Allows user to add/ remove from fields dynamically

Example:

get skills(): FormArray {
    return this,registrationForm.get('skills') as FromArray;
}

addSkill(){
    this.skills.push(new FormControl(''));
}

removeSkill(index: number){
    this.skills.removeAt(index);
}

2. Key Concept of Demonstrated

Data Binding

  1. Interpolation{{val}}
    • One-way binding from component to view
    • Used for displaying component properties
  2. Property Binding[value]="val"
    • One-way binding from component to DOM perporty
    • Update DOM when component perporty changes
  3. Event Binding(click)="method()"
    • One-way binding form view to component
    • Responds to user events
  4. Two-Way Binding[(ngModel)]="val"
    • Combines property and event binding
    • Synchronize view and component

Form Validation

Built-in Validators

Validators.required // Field must have value
Validators.email    // Must be valid email format
Validators.minLength(6) // Minimum length requirement 
Validators.maxLength(6) // Maximum length requirement
Validators.pattern()    // Regex pattern matching

Custom Validetors

No Space Validators

export function noSpaceValidators( control: AbstractControl): ValidationError | null {
    if(control.value !=null && control.value.indexof(' ')!= -1){
        return {noSpace: true};
    }
    retuen null;
}

Abusive Word Validator

export function abusiveWordValidators( control: AbstractControl): ValidationError | null {
    const abusiveWords = ['fool', 'idiot', 'dumb'];
    const controlValue = (control.value as string).toLowerCase();
    const ContainsAbusiveWord = abusiveWords.some(word => 
        controlValue.inclueds(word)
    );
    return ContainsAbusiveWord ? {abusiveWord: true}: null;
}

Validation States

  • touched / untouched: has user interacted with the field?
  • dirty / pristine: has the value changed from initial state?
  • validinvalid : Does it pass all validetors ?

CSS Classes for Validation

Angular automatically adds CSS classes based on validation state:

/*Vaild required fileds -green border */
.ng-valid[required] {
    border-left: 5px solid #42A948;
}

/*Invalid fields - red border */
.ng-invalid:not(form){
    border-left: 5px solid #a94442;
}

/*Touched fields */
.ng-touched { }

/* Dirty field */
.ng-Dirty {  }

3. Potential Interview Questions

Basic level interview Questions

Q1: What's the difference between Template-Driven and Reactive Forms?

Answer:

AspectTemplate-DrivenReactive
SetupFormsModuleReactiveFormsModule
Data ModelImplicit (created by directives)Explicit (created in component)
Data FlowAsynchronousSynchronous
Form ValidationDirectiveFunctions in component
MutabilityMutebleImmutable
ScalablilityLess scalableMore scalable
TestingDifficultEasy

When to use Template-Driven: Simple forms, rapid prototyping, straightforword validation

When to use Reactive: Complex validation logic, dynamic forms , better testability

Q2: Explain form control states in Angular?

Angular:

// Touched vs Untouched 
touched: boolean // User has visited the field (focused and blurred)
untouched: boolean // User has't interacted with the field yet

// Dirty vs Pristien
dirty: boolean  // Value has change form initial state
Pristine: boolean //Value has't change from initial state

// Valid vs Invalid 
valid: boolean // Passes all validation rules 
invalid: boolean // Fails one or more validation rules

Best Practices: Check both invalid and touched before showing errors:

@if(name.invalid && name.touched){
    <div class="alert alert-danger"> 
        Name is requied 
    </div>
}

Q3: How do you access form control values in Reactive Forms?

Answer:

//Method 1: Using get() method
this.login.get('name')?.value;

// Method 2: Using controls property
this.login.controls.name.value;

// Method 3: Using getter (Recommended)
get name(){
    return this.login.controls.name;
}

// In template
@if(name.invalid && name.touched) { }

Intermediate Level Questions

Q4: How do you create and use coustom validators?

Answer:

Step 1: Create the validator function

export function abusiveWordValidator (control: AbstractContorl): ValidationErrors | null {
    const abusiveWords = ['fool', 'idot', 'dunb'];
    const controlValue = (control.value as string).toLowerCase();
    const containsAbusivewords = abusiveWords.some(word => controlValue.includes(word));
    return containsAbusivewords ? {abusiveword: true }: null;
}

Step 2: Apply to FormControl

this.commentForm = new FormGroup({
    comments: new FormControl('', [abusiveWordValidator])
});

Step 3: Display errors in template

@if(comments.invalid && comments.touched){
    <div class= "alert alert-danger">
        @if(comments.error?.['abusiveword']){
            Comments contain abusive word. Please remove them.
        }
    </div>
}

Q5: How do you work with FormArray for dynamic form fields?

Answer:

Component Code:

registrationForm = new FormGroup({
    skills: new FormArray([])
});

get skills(): FormArray {
    return this.registrationForm.get('skills') as FormArray;
}

addSkill(){
    this.skills.push(new FormControl(''));
}

removeSkill(index: number){
    this.skills.removeAt(index);
}

Template Code:

<div formArrayName="skills">
    @for(skill of skills.controls; track $index){
        <div>
            <input [formControlName]="$index">
            <button (click)="removeSkill($index)"> Remove </button>
        </div>
    }
</div>
<button (click)="addSkill()">Add Skill </button>

Q6: How do you display validation errrors conditionally?

Answer:

Using angular 17+ Control Flow (@if)

@if(name.invalid && name.touched){
    <div class="alert alert-denger mt-2">
        @if(name.error?.['required']){
            Name is required
        }
        @if(name.hasError('noSpace')){
            Name can not contain spaces!
        }
        @if(name.error?.['minlength']){
            Password must be at least 
            {{name.error?.['minlength'].requiredLength }} characters long
        }
    </div>
}

Key Points:

  • Check invalid && touched to avoid showing error immediatly
  • Use optional chaining error?.['errorKey'] to prevent null errors
  • Use hasError() menthod for cleaner syntex

Q7: What is the difference between errors?.['required'] ans hasError('required')?

Answer:

Both check for validation errors, but with sutable differences:

// Method 1: Using errors property with optional chaining
@if(name.error?.['required']) { }

// Method 2: Using hasError method
@if(name.hasError('required')) { }

Differences:

  • hasError() is a method that safely checks without throughing errors
  • error?.[] required optional chaining to prevent null reference errors
  • hasError() is more readable and recommended
  • error?.[] is useful when you need the error object value

Advance Level Questions

Q 8: What is FormBuilder and why use it?

Answer:

FormBuilder provides syntactic sugar for creating from controls:

Without FormBuilder:

this.login = new FormGroup({
    name: new FormControl('', [Validators.required]),
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [Validators.required, Validetors.minLength(6)]),
});

With FormBuilder

constructor( private fb: FormBuilder) {}

this.login = this.fb.group({
    name: ['', [Validators.required]],
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(6)]]
});

Bennifits:

  • More concise syntex
  • Less Boilerplate code
  • Easier to read and maintain
  • Same functionallity as manual approch

Q9: How do you handle form submission and validation?

Answer:

// Component
submitted = false;

onSubmit(){
    if(this.login.valid){
        console.log(this.login.value);
        this.submitted = true;
        // Send data to API
    } else {
        // Mark all fileds as touched to show errors
        this.login.markAllAsTouched();
    }
}
<!-- Template -->
<form [formGroup]="login" (ngSubmit)="onSubmit()">
    <!-- form fields -->
     <button [disabled]="login.invalid" type="submit">Submit</button>
</form>

@if(submitted){
    <div class="alert alert-success">
        Form submitted successfully!
        {{login.value | json }}
    </div>
}

Best Practices:

  • Disable submit button when form is invalid
  • Call markAllTouched() to show all errors on submit attemp
  • Show success message after successful submission
  • Reset form if needed: this.login.reset()

10. Explain async validators and when to use them

Answer:

Async validators return Promise or Observable and are used for server-side valication:

// Custom async validator
function usernameAvailableValidator(userService: Userservice){
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
        return userService.checkUserName(control.value).pipe(
            map(available => available ? null : {usernameTaken: true }),
            catchError(() => of(null))
        );
    };
}

// Usage 
this.form = new FormGroup({
    username: new FormControl('', [Validators.required], // Sync validators
               [usernameAvailableValidator(this.userService)] //Async validators 
            )
});

When to Uage:

  • Check if username/email already exists
  • Validate data against database
  • External API validation
  • Any validation required server call

Important: Async validators run AFTER sync validators pass


11: What are standalone components and their benefits?

Answer:

Your components use standalone components (Angular 14+):

@Component ({
    selector: 'app-reactive-form',
    standalone: true,
    imports: [ReactiveFormsModule, JsonPipe, RouterModule, CommonModule],
    templateUrl: './reactive-form.component.html',
    styleUrls: ['./reactive-form.component.css']
})

Benefite: -✅ No need for NgModule -✅ Explicit dependencies (imports array) -✅ Better tree-shaking (smaller bundle size) -✅ Easier to understand component dependencies -✅ Simpler testing setup -✅ Future-proof (Angular's direction)

Migration: Can coexist with NgModule-based components


Q12: How do you implement cross-field validation?

Answer:

Validate multiple fields together:

function passwordMatchValidators (control: AbstractControl): Validators | null {
    const password = control.get('password');
    const confirmPassword = control.get('confirmPassword');

    if(!password ||!confirmPassword) return null;

    return password.value === confirmPassword.value ? null : {passwordMismatch: true };
}

// Apply to FormGroup 
this.form = new FormGroup({
    password: new FormControl(''),
    confirmPassword: new FormControl('')
}, {validators: passwordMismatchValidator });

// Check in template
@if(form.error?.['passwordMismatch'] && form.touched){
    <div>Password do not match </div>
}

Bast Practices

1. Sepration of Concerns

✅ Good: Validators in seperate files


validators/
    |-- abusiveWordValidators.ts
    |-- nospaceValidators.ts

❌ Bad: All logic in component file


2. Type Safe

✅ Good: Using Typescript interfaces

interface Country {
    code: string;
    name: string;
}

countries: Country[] = [{code: 'US', name: 'United states'}, {code:'IN', name: 'India'}];

3. Accessibility

✅ Good: Proper label-input associations

<label for="email" class="from-label">Email:</label>
<input id="email" class="form-control" formControlName="email">

❌ Bad:No label or incorrect association


4. User Experince

✅ Good: Show errors only after user intraction

@if(name.invalid && name.touched){
    <div class="alert alert-danger">Error message</div>
}

❌ Bad: Show errors immediately on page load


5. Component structure

✅ Good: Use getters for form controls

get name(){
    return this.login.controls.name;
}

Benefits:

  • Cleaner template code
  • Type Safty
  • Easier to refactor

6. Form Reset

✅ Good: Proper reset handling

resetForm() {
    this.login.reset();
    this.submitted= false;
} 

7. Responsive Design

✅ Good: Bootstrap classes for responsiveness

<div class="container col-8">
    <div class="mb-3">
        <input class="form-control">
    </div>
</div>

Quick reference Cheat sheet

Form Control Properties

control.value           //current value
control.valid           // is valid?
control.invalid         // is invalid?
control.touched         //User interacted?
control.untouched       // Not interacted yet?
control.dirty           // Value changed?
control.pristine        // value not change?
control.errors          // validation error object
control.hasError('key') // Check specific error

Form control methods

control.setValue(value)         //set value
control.patchValue(value)      // partial update
control.reset()                 // reset to intial
control.markAsTouched()         //Mask as touched
control.updateValueAndUpdate()  // Recalculate validation

Comments

Popular posts from this blog