Frontend Development using React.js or Angular

Chapter 8
Forms, Validation, and User Interaction

Diploma in Full Stack Programming
Frontend Development using React.js or Angular   •   Diploma

Forms, Validation, and User Interaction

Introduction

Forms are the principal interface through which users provide information to a web application. Registration, authentication, checkout, search, booking, feedback, and administrative workflows all depend on reliable form design. A form is not merely a collection of input elements: it is a user-interface component, a data-collection mechanism, a validation boundary, and often the first point at which application data enters a system.

In React and Angular, professional form development requires more than reading values when a button is clicked. The application must maintain a clear relationship between the displayed controls and the data model, validate input at suitable times, communicate errors understandably, support keyboard and assistive-technology users, and handle exceptional cases such as duplicate submissions, network failure, dynamic fields, and file selection.

This chapter presents the principles and practical patterns required to build controlled React forms, template-driven Angular forms, and reactive Angular forms. The examples use TypeScript-style code because explicit types improve reliability in diploma-level application development.

Learning Objectives

By the end of this chapter, a learner should be able to:

  • distinguish controlled, template-driven, and reactive form models;
  • design forms with appropriate HTML semantics and accessible labels;
  • implement synchronous and asynchronous validation;
  • display useful, timely, and non-disruptive feedback;
  • create dynamic fields and repeatable form sections;
  • handle file inputs safely;
  • prevent duplicate submissions and manage server errors;
  • compare React and Angular form approaches in professional projects; and
  • evaluate form designs for usability, security, accessibility, and maintainability.

1. The Form as a Data and Interaction Model

A form has at least four related states:

  1. Value state: the current values entered by the user.
  2. Interaction state: whether a control is untouched, focused, blurred, or modified.
  3. Validation state: whether values satisfy constraints.
  4. Submission state: whether submission is idle, in progress, successful, or failed.

A useful conceptual model is:

User input → form state → validation → feedback → submission → server result
     ↑                                                       ↓
     └────────────── correction and resubmission ────────────┘

The browser provides basic constraint validation through attributes such as required, minlength, maxlength, type="email", min, and max. Client-side validation improves responsiveness, but it is not a security boundary. The server must validate, authorize, normalize, and safely process submitted data independently.

Important terminology

  • Control: one input, select, textarea, checkbox, or file field.
  • Form model: the program representation of form values and state.
  • Controlled input: a React input whose displayed value is determined by component state.
  • Uncontrolled input: an input whose value is primarily managed by the DOM.
  • Template-driven form: an Angular form whose structure and validation are expressed mainly in the template.
  • Reactive form: an Angular form whose controls and validation are explicitly constructed in TypeScript.
  • Touched: a control that has received and then lost focus.
  • Dirty: a control whose value has changed from its initial value.
  • Pristine: a control whose value has not changed.
  • Validity: whether the current value satisfies its validation rules.
  • Cross-field validation: validation involving two or more controls.
  • Asynchronous validation: validation requiring a delayed operation, commonly a server request.

2. Choosing a Form Architecture

React controlled forms

In a controlled form, React state is the source of truth. The input receives a value and reports changes through onChange.

import { FormEvent, useState } from 'react';

type Registration = {
  name: string;
  email: string;
  password: string;
};

type Errors = Partial<Record<keyof Registration, string>>;

export function RegistrationForm() {
  const [data, setData] = useState<Registration>({
    name: '', email: '', password: ''
  });
  const [errors, setErrors] = useState<Errors>({});
  const [status, setStatus] = useState<'idle' | 'submitting' | 'success'>('idle');

  function update(field: keyof Registration, value: string) {
    setData(previous => ({ ...previous, [field]: value }));
  }

  function validate(value: Registration): Errors {
    const next: Errors = {};
    if (!value.name.trim()) next.name = 'Enter your name.';
    if (!/^\S+@\S+\.\S+$/.test(value.email)) {
      next.email = 'Enter a valid email address.';
    }
    if (value.password.length < 8) {
      next.password = 'Use at least 8 characters.';
    }
    return next;
  }

  async function submit(event: FormEvent) {
    event.preventDefault();
    const nextErrors = validate(data);
    setErrors(nextErrors);
    if (Object.keys(nextErrors).length > 0) return;

    setStatus('submitting');
    // await api.register(data);
    setStatus('success');
  }

  return (
    <form onSubmit={submit} noValidate>
      <label htmlFor="name">Name</label>
      <input id="name" value={data.name}
        onChange={e => update('name', e.target.value)}
        aria-invalid={Boolean(errors.name)}
        aria-describedby={errors.name ? 'name-error' : undefined} />
      {errors.name && <p id="name-error" role="alert">{errors.name}</p>}

      <label htmlFor="email">Email</label>
      <input id="email" type="email" value={data.email}
        onChange={e => update('email', e.target.value)}
        aria-invalid={Boolean(errors.email)}
        aria-describedby={errors.email ? 'email-error' : undefined} />
      {errors.email && <p id="email-error" role="alert">{errors.email}</p>}

      <label htmlFor="password">Password</label>
      <input id="password" type="password" value={data.password}
        onChange={e => update('password', e.target.value)} />
      {errors.password && <p role="alert">{errors.password}</p>}

      <button disabled={status === 'submitting'}>
        {status === 'submitting' ? 'Creating account…' : 'Create account'}
      </button>
      {status === 'success' && <p role="status">Account created.</p>}
    </form>
  );
}

Controlled inputs make conditional rendering, immediate validation, derived values, and predictable testing straightforward. Their disadvantages include more state-management code and the risk of performance problems when very large forms re-render on every keystroke.

A common React error is switching an input between controlled and uncontrolled modes. For example, value={undefined} at first and value="abc" later can produce warnings and confusing behavior. Initialize text fields with '', arrays with [], and booleans with false.

Angular template-driven forms

Template-driven forms use directives such as [(ngModel)], #form="ngForm", required, and minlength. They are appropriate for small forms with straightforward rules.

<form #profileForm="ngForm" (ngSubmit)="save(profileForm)" novalidate>
  <label for="displayName">Display name</label>
  <input id="displayName" name="displayName"
         [(ngModel)]="profile.displayName"
         required minlength="2"
         #displayName="ngModel"
         [attr.aria-invalid]="displayName.invalid && displayName.touched"
         [attr.aria-describedby]="displayName.invalid ? 'displayName-error' : null">

  <p id="displayName-error"
     *ngIf="displayName.invalid && displayName.touched" role="alert">
    Display name must contain at least two characters.
  </p>

  <button type="submit" [disabled]="profileForm.invalid || saving">
    {{ saving ? 'Saving…' : 'Save profile' }}
  </button>
</form>
profile = { displayName: '' };
saving = false;

save(form: NgForm) {
  if (form.invalid) {
    form.control.markAllAsTouched();
    return;
  }
  this.saving = true;
  // Submit to a service, then reset saving in success and error handlers.
}

Every named control must have a name attribute for Angular to register it with the form. Template-driven forms are readable for simple screens, but complex cross-field rules and dynamic structures can become difficult to trace.

Angular reactive forms

Reactive forms construct a model explicitly in TypeScript. The principal classes are FormControl, FormGroup, and FormArray.

import { FormControl, FormGroup, Validators } from '@angular/forms';

profileForm = new FormGroup({
  displayName: new FormControl('', {
    nonNullable: true,
    validators: [Validators.required, Validators.minLength(2)]
  }),
  email: new FormControl('', {
    nonNullable: true,
    validators: [Validators.required, Validators.email]
  })
});

save() {
  if (this.profileForm.invalid) {
    this.profileForm.markAllAsTouched();
    return;
  }
  const value = this.profileForm.getRawValue();
  // Send value to an API service.
}
<form [formGroup]="profileForm" (ngSubmit)="save()" novalidate>
  <label for="displayName">Display name</label>
  <input id="displayName" formControlName="displayName">
  <p *ngIf="profileForm.controls.displayName.touched &&
            profileForm.controls.displayName.hasError('required')"
     role="alert">Display name is required.</p>

  <label for="email">Email</label>
  <input id="email" formControlName="email">
  <p *ngIf="profileForm.controls.email.touched &&
            profileForm.controls.email.hasError('email')"
     role="alert">Enter a valid email address.</p>

  <button type="submit" [disabled]="profileForm.invalid">Save</button>
</form>

Reactive forms provide an explicit, testable model and are generally preferred for multi-step, dynamic, or highly validated workflows. They require more initial code but scale better as requirements grow.

RequirementReact controlledAngular template-drivenAngular reactive
Small simple formSuitableVery suitableSuitable
Complex validationSuitableLess convenientVery suitable
Dynamic repeated fieldsSuitable with state logicPossible but cumbersomeVery suitable with FormArray
Explicit unit testingSuitableModerateVery suitable
Main source of form modelComponent stateTemplate directives and component modelTypeScript form model

3. Validation Design

Validation should be layered rather than concentrated in one location.

  1. HTML constraints provide immediate browser-level semantics.
  2. Client validation gives prompt, contextual feedback.
  3. Server validation is authoritative because clients can be modified or bypassed.
  4. Business validation checks rules such as account status, inventory, uniqueness, or authorization.

A good error message identifies the problem and, where possible, the correction: “Enter a date in YYYY-MM-DD format” is more useful than “Invalid value.” Avoid validating aggressively while a user is still typing. A practical policy is to validate on blur for ordinary controls, on submit for the entire form, and on change after an error has been shown.

Common validation rules

  • requiredness;
  • length and range limits;
  • format, such as email or telephone number;
  • permitted values and enumerations;
  • relationships between fields, such as password confirmation;
  • uniqueness or availability checked by the server; and
  • file type, size, and quantity restrictions.

Regular expressions can validate simple formats, but they should not attempt to encode every possible interpretation of an international name, address, or telephone number. Prefer established platform or library facilities where appropriate.

Cross-field validation

A password-confirmation rule cannot be attached meaningfully to only one independent value; it compares two values. In Angular reactive forms, attach the validator to the group. In React, run the comparison in the form-level validation function. The message should be associated with the relevant field or group and should not appear until the user has supplied enough information to make the comparison meaningful.

Asynchronous validation

Username availability and invitation-code verification may require a server request. Debounce requests where appropriate, cancel obsolete requests, and distinguish three states: not checked, checking, and checked. A failed network request is not necessarily proof that the value is invalid; it may require a retry or a general service-error message.

4. Dynamic Fields and Complex Structures

Dynamic forms occur when users can add addresses, qualifications, invoice lines, dependants, or survey answers. Each repeated section needs a stable identity, a clear heading, and controls with unique labels and identifiers.

In React, store an array of objects and use a stable key generated when the item is created. Do not use the array index as a key if items can be inserted or removed, because focus and component state may move to the wrong item.

In Angular reactive forms, use FormArray:

import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';

addresses = new FormArray([
  new FormGroup({
    street: new FormControl('', { nonNullable: true, validators: Validators.required }),
    city: new FormControl('', { nonNullable: true, validators: Validators.required })
  })
]);

addAddress() {
  this.addresses.push(new FormGroup({
    street: new FormControl('', { nonNullable: true, validators: Validators.required }),
    city: new FormControl('', { nonNullable: true, validators: Validators.required })
  }));
}

removeAddress(index: number) {
  if (this.addresses.length > 1) this.addresses.removeAt(index);
}

The interface should make the add and remove actions obvious. If removing a section could destroy substantial work, ask for confirmation or provide an undo action.

5. File Inputs

File inputs are deliberately special. Browsers do not allow applications to set a user’s local file path programmatically. A file input should be read from a change event, and the selected File object can be sent using FormData.

function handleFile(event: React.ChangeEvent<HTMLInputElement>) {
  const file = event.target.files?.[0];
  if (!file) return;

  const allowed = ['image/png', 'image/jpeg'];
  const maximumBytes = 5 * 1024 * 1024;

  if (!allowed.includes(file.type)) {
    setError('Choose a PNG or JPEG image.');
    return;
  }
  if (file.size > maximumBytes) {
    setError('The image must be no larger than 5 MB.');
    return;
  }
  setSelectedFile(file);
}

async function upload(file: File) {
  const body = new FormData();
  body.append('avatar', file);
  await fetch('/api/avatar', { method: 'POST', body });
}

The MIME type supplied by a browser is not sufficient for security. A server should enforce upload size, inspect file signatures where appropriate, reject dangerous content, store uploads outside executable paths, generate safe names, and apply authorization. Never trust a client-provided filename or extension. If previews use URL.createObjectURL, release the object URL with URL.revokeObjectURL when it is no longer needed.

6. Accessible and User-Friendly Interaction

Accessibility is a design requirement, not a final styling step.

  • Use a real <form> and a submit button.
  • Associate every control with a visible <label> using matching for and id values.
  • Use fieldsets and legends for related radio buttons or checkboxes.
  • Preserve logical keyboard order and visible focus indicators.
  • Do not communicate errors through colour alone.
  • Link error text to controls with aria-describedby.
  • Use aria-invalid="true" only when the value is actually invalid.
  • Announce important status changes with an appropriate live region such as role="status".
  • Move focus to a useful location after submission failure, while avoiding disruptive focus movement on every keystroke.
  • Ensure touch targets, contrast, and text resizing work across devices.

Error summaries are valuable for long forms. The summary should list links to invalid fields and receive focus after a failed submission, allowing keyboard users to reach the first problem efficiently. Inline messages should remain next to their controls because users often need the local context.

7. Submission, Feedback, and Failure Handling

Submission is an asynchronous state machine rather than a single event:

idle → validating → submitting → succeeded
              ↘                 ↘
              invalid            failed → retry

Prevent the default browser submission when JavaScript owns the process, but do not remove the semantic form structure. Disable or otherwise protect the submit action while a request is in progress. The protection must also exist in the server or API layer because users can submit through multiple browser tabs or manually repeat requests.

A robust submission handler should:

  1. prevent accidental navigation;
  2. validate the current model;
  3. show field errors and focus an appropriate location if invalid;
  4. mark the request as in progress;
  5. send only the intended data;
  6. handle success, validation errors, authorization errors, and network errors separately;
  7. preserve user-entered data when recovery is possible; and
  8. restore an actionable interface in every completion path.

Do not replace a complete form with a vague “Something went wrong.” Explain whether the user should correct a field, sign in again, retry later, or contact support. Avoid exposing stack traces, database details, access tokens, or internal identifiers.

8. Security, Privacy, and Governance

Forms collect personal and sometimes sensitive information. Professional development therefore includes data minimization, purpose limitation, access control, retention rules, and secure transmission.

  • Use HTTPS for forms carrying credentials or personal data.
  • Use server-side authorization and validation.
  • Protect authenticated state against session and request-forgery threats using the framework’s recommended mechanisms.
  • Do not place passwords, tokens, or unnecessary personal data in logs.
  • Do not rely on hidden fields to enforce prices, roles, permissions, or ownership.
  • Escape or safely render user content to reduce injection risks.
  • Obtain meaningful consent where required, especially for marketing or sensitive processing.
  • Provide alternatives when a workflow would otherwise exclude users with disabilities.

Privacy notices should be understandable and proportionate. A form should not request a date of birth, government identifier, or precise location merely because the field is convenient to add.

9. Applied Case: Course-Enrolment Form

This is an illustrative case. A college is building an enrolment form for a programming diploma. The form collects a student’s name, email, selected modules, emergency contact, and proof-of-identity file.

Background and problem

The initial prototype places all data in one large component. It validates only after the server responds, loses entered values when an upload fails, allows duplicate submissions, and displays red borders without text explanations. Keyboard testing reveals that the emergency-contact controls are not grouped and that errors are not announced.

Analysis

The requirements contain simple fields, repeated module selections, a cross-field relationship, asynchronous checks, and a file upload. A single undifferentiated validation function would be difficult to maintain. The team chooses a reactive Angular form because its explicit model supports FormArray, group-level validation, and unit testing. A comparable React implementation would use a typed state object, a stable array of module rows, and a form-level validator.

Approach and solution

The team divides the form into sections with headings and a progress indicator. Required fields are marked in text, not colour alone. Module choices are represented as a FormArray, with a rule requiring at least two modules. The email is checked locally for format and asynchronously for an existing student record. The identity file is limited by server-enforced type and size rules. On submission, the form marks all controls as touched, presents an error summary, and focuses that summary. During submission, the button is disabled and its text changes to “Submitting…”. API validation errors are mapped back to fields; a network error preserves all values and offers retry.

The server recalculates enrolment eligibility, verifies that the chosen modules exist, checks authorization, scans the uploaded file, and returns structured errors without revealing sensitive system details.

Outcome

The revised design gives users immediate local feedback while preserving server authority. Keyboard users can reach each control and its message, duplicate submissions are reduced, and failed uploads do not destroy the rest of the application. The explicit form model also allows unit tests for minimum module count, invalid email, and file constraints.

Learning points

The central lesson is that form quality emerges from coordinated state modelling, validation, accessibility, asynchronous control, and server-side security. A visually attractive form can still be unusable or unsafe if these dimensions are ignored.

10. Testing Form Behaviour

Test form logic at several levels:

  • Unit tests: individual validators, normalizers, and state transitions.
  • Component tests: typing, blur behaviour, error rendering, disabled states, and successful submission.
  • Integration tests: interaction with API services and error mapping.
  • End-to-end tests: keyboard navigation, real submission flows, file selection, and recovery from network failure.
  • Accessibility tests: labels, names, focus order, announcements, contrast, and screen-reader interpretation.

Important cases include empty submission, whitespace-only input, boundary lengths, invalid formats, corrected errors, rapid repeated clicks, expired sessions, server-side field errors, slow responses, cancelled requests, and failed file uploads. Tests should verify behaviour rather than implementation details wherever possible.

11. Contemporary Practice and Limitations

Modern frameworks increasingly provide typed forms, schema validation, server actions, and component libraries. These tools can reduce repetitive code, but they do not eliminate design decisions. A schema library may centralize rules, yet the same rule may need a user-friendly message, localization, and server-side enforcement. Automatic validation can also become excessive, causing unnecessary requests or distracting feedback.

Client-rendered applications must also consider progressive enhancement and unreliable networks. Where the platform permits, a semantic form and a server endpoint provide a more resilient baseline. Internationalization requires care with names, addresses, dates, decimal separators, right-to-left layouts, and translated error messages. Artificial intelligence may assist developers in generating validators or test cases, but generated code must be reviewed for accessibility, privacy, security, and false assumptions about user data.

Best-Practice Checklist

Before releasing a form, confirm that:

  • each control has a meaningful label and an appropriate input type;
  • the model has stable initial values;
  • validation rules are documented and enforced on the server;
  • errors appear at a sensible time and explain correction;
  • keyboard and assistive-technology users can complete the workflow;
  • dynamic controls have stable identities and unique labels;
  • file uploads are restricted and scanned server-side;
  • submission cannot accidentally occur twice;
  • loading, success, validation, authorization, and network states differ;
  • user input is preserved where recovery is possible; and
  • personal data is collected, stored, and disclosed responsibly.

Chapter Summary

Forms combine data modelling, interaction state, validation, submission, feedback, accessibility, and security. React controlled forms place component state at the centre of the interaction. Angular template-driven forms offer a concise approach for simple screens, whereas Angular reactive forms provide an explicit and testable model for complex, dynamic, and cross-field workflows. Validation should be layered: browser and client checks improve usability, while server checks remain authoritative. Dynamic fields require stable identities; file inputs require strict client and server controls; and asynchronous submission requires clear states, duplicate-submission protection, and recovery paths. Accessible labels, focus management, error associations, semantic grouping, and non-colour feedback are essential. Finally, professional form development treats privacy, authorization, data minimization, testing, and internationalization as part of the feature rather than optional additions.

Key Terms

Controlled input: A React input whose displayed value is determined by application state.

Uncontrolled input: An input whose current value is primarily maintained by the DOM.

Template-driven form: An Angular form configured mainly through template directives.

Reactive form: An Angular form represented explicitly by controls and groups in TypeScript.

FormControl: An Angular object representing the value and state of one control.

FormGroup: A collection of related controls managed as one unit.

FormArray: A collection of controls or groups whose length can change dynamically.

Validation: The process of determining whether input satisfies defined rules.

Synchronous validation: Validation completed immediately using available values.

Asynchronous validation: Validation requiring a delayed operation, such as an API request.

Touched: A state indicating that a control has received and lost focus.

Dirty: A state indicating that a control’s value has changed.

Cross-field validation: A rule that compares or combines multiple controls.

Error summary: A consolidated list of form errors, often linking to invalid fields.

FormData: A browser interface for constructing multipart form submissions, including files.

Progressive enhancement: Building a functional baseline that can be improved by client-side features.

Data minimization: Collecting only the personal data needed for a stated purpose.

Idempotency: A property that makes repeated equivalent requests produce a safe, controlled result.

Live region: An accessibility mechanism that announces dynamic content changes.

Review Questions

Short Answer Questions

  1. What is the difference between a controlled and an uncontrolled input in React?
  2. Why must server-side validation supplement client-side validation?
  3. What are the purposes of the touched and dirty states?
  4. When is an Angular FormArray useful?
  5. Why should a file’s browser-reported MIME type not be trusted alone?

Long Answer Questions

  1. Compare React controlled forms, Angular template-driven forms, and Angular reactive forms. Discuss suitable use cases and trade-offs.
  2. Explain how a form should communicate validation errors to sighted keyboard users and screen-reader users.
  3. Describe a robust asynchronous form-submission workflow, including loading, success, validation failure, and network failure.
  4. Discuss the security and privacy responsibilities involved in collecting personal data and uploading files.
  5. Explain how dynamic fields should be designed and tested in a professional application.

Analytical / Application Questions

  1. Design the validation and interaction model for a checkout form containing customer details, a shipping address, a billing-address option, and payment confirmation. Justify your choice of framework form architecture.
  2. A registration form sends an availability request on every keystroke and sometimes displays an old response after the user has changed the username. Diagnose the problem and propose a corrected asynchronous-validation strategy.
  3. Evaluate an enrolment form that displays only red borders, resets after every server error, and permits repeated clicks on its submit button. Identify the usability, accessibility, reliability, and security defects, and propose improvements.

Multiple Choice Questions with Answers

Question 1

In a React controlled input, which source determines the value displayed in the input?

A. The component or application state

B. The server database only

C. The browser cache

D. The CSS stylesheet

Correct Answer: A. The component or application state

Question 2

Which Angular form approach is generally most suitable for complex dynamic forms with repeatable sections?

A. Template-driven forms only

B. Reactive forms using FormArray

C. A CSS form grid

D. A browser bookmark

Correct Answer: B. Reactive forms using FormArray

Question 3

What is the primary reason server-side validation is required even when client-side validation exists?

A. Client code can be bypassed or modified

B. Client validation always runs too slowly

C. HTML cannot display labels

D. Servers cannot process strings

Correct Answer: A. Client code can be bypassed or modified

Question 4

Which attribute associates an input with descriptive error text for assistive technologies?

A. aria-describedby

B. aria-hidden

C. tabindex="-1" only

D. autocomplete="off"

Correct Answer: A. aria-describedby

Question 5

What is a suitable purpose of a FormArray?

A. Storing a variable-length collection of controls

B. Encrypting uploaded files

C. Replacing the HTTP protocol

D. Styling invalid controls

Correct Answer: A. Storing a variable-length collection of controls

Question 6

Why should an application avoid using an array index as the key for removable repeated React fields?

A. Items may receive unstable identity, causing state or focus to move incorrectly

B. Indexes cannot be stored in JavaScript arrays

C. Keys are used only for CSS colours

D. React forbids all numeric values

Correct Answer: A. Items may receive unstable identity, causing state or focus to move incorrectly

Question 7

Which practice is appropriate for an uploaded file?

A. Trust the filename extension as proof of safety

B. Validate size and type on the server and apply safe storage controls

C. Allow unlimited files by default

D. Place the file directly in an executable directory

Correct Answer: B. Validate size and type on the server and apply safe storage controls

Question 8

What should a form commonly do when submission fails because of a network error?

A. Erase all user-entered data

B. Preserve recoverable data and offer a clear retry path

C. Report a successful submission

D. Expose the server stack trace

Correct Answer: B. Preserve recoverable data and offer a clear retry path

Question 9

Which Angular form control state indicates that the user has changed a control’s value?

A. Dirty

B. Untouched

C. Disabled

D. Pending-only

Correct Answer: A. Dirty

Question 10

Why should a submit button usually be protected while a request is in progress?

A. To reduce accidental duplicate submissions

B. To prevent labels from being read

C. To disable server validation permanently

D. To hide all validation messages

Correct Answer: A. To reduce accidental duplicate submissions

Frontend Development using React.js or Angular
Chapter 8 of 14