Advanced Frontend Frameworks (React.js, Angular)

Chapter 6
Routing, Navigation, Forms and Validation

Post Graduate Diploma in Full Stack Development
Advanced Frontend Frameworks (React.js, Angular)   •   Post Graduate

6. Routing, Navigation, Forms and Validation

Introduction and Context

A modern frontend application is not merely a collection of screens. It is an interactive system in which users move through information, submit data, receive feedback, and perform tasks that may span several views. Routing determines which view is presented for a particular URL or application state. Navigation connects those views into a coherent workflow. Forms provide the principal interface for collecting user input, while validation protects data quality, guides users, and supports secure communication with backend services.

In React.js and Angular, these concerns are closely related. A registration form may be accessible through a public route, a checkout form may require authentication, and a multi-step application form may need to preserve data while the user navigates between nested routes. Poorly designed routing produces broken browser history, inaccessible screens, and unauthorized data exposure. Poorly designed forms create confusion, invalid submissions, and avoidable server errors.

This chapter examines routing, navigation, forms, validation, error handling, and accessible workflows in both frameworks. The emphasis is not on memorising API syntax, but on understanding design decisions: when to use nested or lazy-loaded routes, how to protect application areas, how to align client and server validation, and how to design user journeys that remain reliable under real-world conditions.

Learning Objectives

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

1. Explain the role of client-side routing and distinguish browser, hash, and server-supported navigation.

2. Design nested, parameterised, protected, and lazy-loaded routes in React.js and Angular.

3. Select appropriate navigation techniques for links, redirects, workflow progression, and data-aware transitions.

4. Compare React controlled forms and Angular reactive and template-driven forms.

5. Apply synchronous, asynchronous, and schema-based validation while preserving server authority.

6. Design meaningful error, loading, and empty states.

7. Build accessible and resilient form workflows.

8. Evaluate routing and form architectures in professional applications.

1. Routing as an Application Architecture

Routing maps a location, commonly a URL, to a view and its associated data. In a single-page application (SPA), the browser loads the application shell once and the frontend framework changes the displayed component without requesting a complete HTML document for every transition. This improves continuity and can reduce network overhead, but it also transfers responsibilities traditionally handled by the server to the client application.

A route normally contains a path, a component or element to render, and optional metadata such as permissions, titles, breadcrumbs, or data-loading requirements. A route may also contain child routes. For example, an administrative area might use the structure shown below:

```text

/admin

/dashboard

/users

/users/:userId

/settings

```

The distinction between a URL and a view is important. A URL should express a meaningful application state, not merely the order in which a user clicked buttons. Meaningful URLs support bookmarking, browser history, deep linking, testing, analytics, and assistive technology. A route such as `/orders/845/edit` communicates considerably more than `/screen3`.

Routing strategies

History-based routing uses the browser History API and produces ordinary-looking URLs such as `/products/42`. It is generally preferred for professional applications, but the web server must be configured to return the SPA entry document for unknown frontend routes. Without this configuration, refreshing a deep link can result in a server-side 404.

Hash routing produces URLs such as `/#/products/42`. Because the fragment is not sent to the server, it can be useful in static hosting environments with limited rewrite support. Its disadvantages include less attractive URLs and weaker integration with some server-side tools.

Server-side routing and client-side routing are complementary rather than mutually exclusive. A server may deliver the initial application, enforce security policies, and provide APIs, while the client router manages transitions after bootstrapping. Client-side route protection must never be treated as the sole security boundary; authorization must also be enforced on the server.

Nested and parameterised routes

Nested routes represent containment or a shared layout. A parent route can render navigation, a toolbar, or contextual information while a child route occupies an outlet. In React Router, nested routes commonly render through an `Outlet`; in Angular, child routes render through a nested `router-outlet`.

Parameters allow a route to identify a resource:

```text

/customers/:customerId/invoices/:invoiceId

```

A parameter should be validated before it is used to fetch data. The application must distinguish an invalid identifier, a missing resource, and a temporary loading failure. These states should not all be represented as a blank screen.

Query parameters are appropriate for optional, shareable state such as filtering, sorting, pagination, and search terms:

```text

/products?category=books&page=2&sort=price

```

Sensitive information should not be placed in URLs because URLs may be stored in browser history, server logs, analytics systems, and referrer headers.

2. Routing and Navigation in React.js and Angular

React.js routing

React itself does not prescribe a router. In professional applications, a routing library such as React Router is commonly used. A declarative route configuration may express public routes, nested layouts, dynamic segments, and fallback routes:

```jsx

const routes = [

{

path: "/app",

element: <ProtectedLayout />,

children: [

{ path: "dashboard", element: <Dashboard /> },

{ path: "projects/:projectId", element: <ProjectDetails /> }

]

},

{ path: "/login", element: <Login /> },

{ path: "*", element: <NotFound /> }

];

```

A protected layout normally checks authentication state and either renders an outlet or redirects to the login route. The intended destination may be preserved as state or as a carefully encoded return parameter. Redirects should avoid loops: a login route must not itself be protected by the same guard.

React navigation can be declarative, using links and navigation components, or programmatic, using a navigation hook. Declarative links are preferable for ordinary user navigation because they support familiar browser behaviours such as opening a link in a new tab. Programmatic navigation is suitable after a successful mutation, such as redirecting to an order confirmation page after checkout.

Data-aware React routing can associate data loading and mutation behaviour with routes. A route may display a pending state while a loader executes and an error element when loading fails. This approach prevents every component from independently inventing loading and error conventions. Nevertheless, a design must clarify cache ownership, cancellation, stale data, and what happens when a user navigates away during a request.

Angular routing

Angular provides a router as a core platform service. Routes are configured as an array and registered with the router:

```typescript

const routes: Routes = [

{

path: 'app',

component: AppShellComponent,

canActivate: [authGuard],

children: [

{ path: 'dashboard', component: DashboardComponent },

{ path: 'projects/:projectId', component: ProjectDetailsComponent }

]

},

{ path: 'login', component: LoginComponent },

{ path: '**', component: NotFoundComponent }

];

```

Angular supports guards such as `CanActivate`, `CanActivateChild`, and `CanDeactivate`. Modern Angular applications may implement these as functional guards. A guard can return a boolean, a URL tree, or an asynchronous result. Returning a redirect representation is generally preferable to imperatively navigating inside the guard because it makes the routing decision explicit.

Resolvers can retrieve data before a route is activated. They are useful when a screen cannot render meaningfully without a particular resource, but they should not be used indiscriminately. Blocking navigation for slow or optional data can make the application feel unresponsive. A hybrid strategy often works best: resolve essential identity data and load supplementary information after activation.

Angular lazy loading can load a feature route only when it is requested. Standalone components and feature route arrays can be loaded with `loadComponent` or `loadChildren`. Lazy loading reduces the initial bundle and establishes a useful architectural boundary, but it introduces additional loading states and requires attention to shared dependencies and preloading policy.

A comparative design perspective

React routing is assembled from library capabilities and application conventions, whereas Angular routing is an integrated framework subsystem. React offers considerable freedom in data-fetching and form choices; Angular supplies more standardised mechanisms. In both cases, the architectural questions remain similar:

  • Which routes are public, authenticated, or role-restricted?
  • Which data is essential before activation?
  • Which features deserve a separate lazy-loaded boundary?
  • How should refresh, back navigation, and deep linking behave?
  • What information should be preserved when a user is redirected?

3. Protected, Lazy-Loaded, and Data-Aware Navigation

Authentication answers the question, “Who is the user?” Authorization answers, “What may this user do?” A route guard may improve the user experience by hiding or redirecting from unavailable features, but it cannot replace backend authorization. An attacker can call an API directly even if a button or route is hidden.

Role and permission decisions should be represented clearly. A broad `isAdmin` flag may be adequate for a small application, but larger systems often use capabilities such as `invoice.read`, `invoice.approve`, and `user.manage`. The frontend should use the least information necessary and should fail safely when permission data is unavailable.

Lazy loading is both a performance technique and a modularity technique. A large application can separate customer, reporting, and administration features into independent route groups. However, lazy loading does not automatically improve performance. Excessive fragmentation can create many small requests and delayed transitions. Teams should examine bundle analysis, route frequency, device constraints, and network conditions before selecting boundaries.

Data-aware navigation links the route transition to the state of data operations. A save button may remain disabled while a submission is pending, and a successful save may navigate to a detail page carrying the newly created identifier. If the operation fails, the user should remain on the form with entered values intact and an actionable error message. Navigation should not occur merely because a request was initiated; it should follow confirmed business success.

Navigation also includes document title, focus, breadcrumbs, and scroll behaviour. After a route change, the page title should describe the new view. Keyboard focus should be moved to a meaningful heading or main landmark when appropriate, while avoiding disruptive focus changes for minor updates. Scroll restoration should be predictable: a new page often starts at the top, whereas returning to a filtered list may reasonably restore the previous position.

4. Forms as State and Interaction Systems

A form is more than a set of input controls. It is a state machine involving initial values, user edits, touched or visited fields, validation status, submission status, server responses, and possible navigation. Treating forms as state systems helps developers reason about complex workflows such as draft saving, multi-step registration, and conditional fields.

A form commonly contains:

  • Value state: the current value of each field.
  • Interaction state: whether a field has been focused, blurred, or modified.
  • Validity state: whether current values satisfy rules.
  • Submission state: idle, submitting, succeeded, or failed.
  • Server state: field-specific and general errors returned by an API.
  • Workflow state: the current step, saved draft, or completion status.

Validation should be timed carefully. Showing errors before a user has interacted with a field can create unnecessary anxiety. Delaying all feedback until final submission can make correction difficult. A balanced approach validates on blur or after meaningful interaction, provides a summary on submission, and preserves server errors until the relevant value changes or the error is resolved.

React forms

React forms are commonly controlled: the input value is stored in component state and updated through an event handler. Controlled forms make conditional rendering, derived validation, and synchronisation with application state explicit. They can, however, cause frequent renders in very large forms if not designed carefully.

A simplified pattern is:

```jsx

const [form, setForm] = useState({ email: '', password: '' });

const [errors, setErrors] = useState({});

function updateField(event) {

const { name, value } = event.target;

setForm(current => ({ ...current, [name]: value }));

}

```

Form libraries can reduce boilerplate, track touched state, optimise rendering, and integrate with schema validators. Uncontrolled inputs, accessed through refs, can also be appropriate for simple forms or performance-sensitive cases. The decision should be based on complexity, not ideology. A form with conditional sections, cross-field dependencies, and server errors generally benefits from a structured form library or carefully designed state model.

Angular reactive forms

Angular reactive forms define a form model in TypeScript. `FormControl`, `FormGroup`, and `FormArray` represent individual values, groups, and dynamic collections. A typical form may be created as follows:

```typescript

profileForm = this.fb.group({

name: ['', [Validators.required]],

email: ['', [Validators.required, Validators.email]],

skills: this.fb.array([])

});

```

Reactive forms are explicit, testable, and well suited to dynamic or complex forms. Validators can be attached to controls or groups. Group-level validation is appropriate for relationships such as password confirmation or date ranges. Asynchronous validators can check a condition against a service, although they should be debounced and protected against race conditions.

Angular template-driven forms

Template-driven forms use directives such as `[(ngModel)]`, `ngForm`, and validation attributes in the template. They are concise and effective for small forms with straightforward relationships. Their logic is less centralised than a reactive form model, making complex dynamic structures harder to reason about and test.

The choice is therefore practical. Template-driven forms suit uncomplicated data-entry screens. Reactive forms are usually preferable for multi-step workflows, dynamic arrays, extensive validation, and forms whose structure is generated from metadata.

5. Validation: From User Guidance to Domain Integrity

Validation operates at several layers. HTML constraints such as `required`, `type="email"`, and `minlength` provide immediate browser-level assistance. Framework validators provide application-level feedback. Schema libraries can express reusable object rules. The server performs authoritative validation because the client can be bypassed or may use outdated rules.

A useful validation model distinguishes:

1. Syntactic validation: Is the value in an acceptable format?

2. Semantic validation: Does it make sense in the domain?

3. Relational validation: Is it consistent with other fields?

4. Authorization validation: Is this user allowed to perform the operation?

5. Persistence validation: Can the value be stored without violating database constraints?

Schema validation libraries can define an object contract once and apply it at boundaries. For example, a registration schema may require a valid email, a minimum password length, and matching confirmation. The schema should not be mistaken for a security mechanism. It improves consistency and error mapping, but the server remains authoritative.

Error messages should describe the correction required rather than merely announce failure. “Enter an email address in the format [email protected]” is more useful than “Invalid input.” Messages must not disclose sensitive information, such as whether an account exists, when doing so could enable account enumeration.

Asynchronous validation requires special care. A username-availability request should be debounced, cancelled or ignored when stale, and treated as advisory until the final server submission. Even if availability appears positive, another user may claim the name before submission. The backend must enforce uniqueness.

6. Error Handling and Accessible Workflows

A robust application anticipates failure at route loading, data retrieval, validation, authentication, authorization, submission, and network layers. These failures should be classified rather than collapsed into a generic error.

A route-level error boundary or error component can present a recoverable message while preserving the rest of the application shell. Form errors should be attached to fields when possible and shown in a summary for users who need an overview. Global failures, such as an unavailable service, may require a banner or status region.

An accessible form associates every control with a visible label. Placeholder text is not a substitute for a label because it disappears during entry and may have inadequate contrast. Errors should be programmatically associated using mechanisms such as `aria-describedby`, and invalid controls may use `aria-invalid="true"`. A summary should provide a way to navigate to the first or each invalid field.

Keyboard users must be able to reach every control, understand the current step, and submit without relying on a mouse. Focus indicators should remain visible. Custom widgets should not be built when native controls provide the required behaviour. Dynamic error messages should use an appropriate live-region strategy without repeatedly interrupting users.

Accessible workflow design also considers timing and cognition. A disabled submit button alone is not an adequate explanation of why submission cannot proceed. Users should be able to review entered information, recover from errors, and avoid losing data when a request fails. Destructive navigation away from dirty forms may require a confirmation mechanism, but such prompts should be used selectively because excessive warnings create alert fatigue.

Illustrative Case Study: A Secure Project-Management Portal

Background

A fictional organisation, Northstar Services, is developing a project-management portal using React for its customer interface and Angular for an internal operations console. Customers can view projects, upload documents, and submit change requests. Operations staff can approve requests and manage user permissions.

Situation and problem

The first prototype placed all screens in one bundle, used buttons with imperative navigation, and validated forms only after API submission. Users reported that browser back navigation behaved unpredictably, refreshes on project pages produced 404 errors, and failed change-request submissions erased entered descriptions. An accessibility review found unlabeled controls and error messages that were not announced to screen readers.

Analysis

The problems were architectural rather than isolated defects. Routes did not represent resource identity consistently. Authentication checks were applied only to visible menu items, not to route transitions or APIs. Form state was mixed with presentation state, and the server returned field errors in a format the client did not map to controls. The operations console also loaded reporting and administration features for every employee, increasing the initial bundle.

Approach and solution

The team redesigned the route model. Customer routes used a protected application shell with nested project routes:

```text

/app/projects

/app/projects/:projectId

/app/projects/:projectId/changes/new

```

The operations console used an Angular protected shell with permission-aware child routes. Administration and reporting were lazy-loaded. The server was configured to return the SPA entry document for valid frontend paths, while API routes retained normal server errors.

In React, the change-request form used a structured form model with touched state, client-side schema validation, and a submission state. The API returned errors in a standard format containing a general message and a map of field errors. The form preserved values after failure, displayed field errors beside controls, and displayed a summary at the top. On confirmed success, navigation moved to the change-request detail route and included a success notification.

The Angular operations form used a reactive `FormGroup` with group-level validation for approval dates and asynchronous permission checks. A route resolver loaded the request required for the approval screen, while nonessential audit information loaded after activation. Guards returned explicit redirects for unauthenticated users and denied users without the required capability.

The team added document-title updates, focus management to the main heading after route changes, keyboard-accessible error summaries, and a confirmation prompt only when a dirty form would be abandoned. Server-side authorization and validation remained mandatory.

Outcome

The redesigned system provided predictable deep links, smaller initial feature bundles, consistent error handling, and recoverable submission failures. The most significant improvement was not a particular framework feature but the alignment of URLs, data boundaries, permissions, and form state with the actual business workflow.

Learning points

1. Route design should reflect domain resources and user tasks.

2. Client guards improve experience but cannot enforce security alone.

3. Lazy loading should follow meaningful feature boundaries.

4. Form values must survive recoverable failures.

5. Accessibility is part of workflow correctness, not a final visual enhancement.

6. A standard error contract between frontend and backend reduces application complexity.

Professional Practices, Risks, and Governance

Teams should establish conventions for route naming, permission metadata, error response formats, loading states, and form submission behaviour. Code review should examine whether a new route is deep-linkable, whether unauthorized data can be requested directly, and whether failure states are usable.

Common mistakes include placing authorization tokens or sensitive data in query strings, trusting hidden controls as security, redirecting before a mutation succeeds, using one generic error message for every failure, and allowing stale asynchronous validation results to overwrite current state. Another frequent mistake is adding a confirmation dialog to every navigation event, which can make ordinary browsing frustrating.

Routing can expose personal or confidential information through URLs, browser history, analytics, and referrer data. Applications should minimise sensitive route parameters, apply appropriate transport security, configure content security and framing policies, and avoid logging form values. Passwords and payment details require specialised handling and should not be stored in ordinary frontend state or browser storage without a justified security design.

From a governance perspective, permission changes should be auditable on the server. Client-side route metadata can support user experience and testing, but the backend must record and enforce the actual decision. Validation rules should be versioned when they affect contractual data, and changes should be tested across both client and server implementations.

Current Developments and Best Practices

Contemporary frontend applications increasingly combine route-level code splitting with data loaders, server rendering, streaming, and progressive enhancement. These techniques can improve initial rendering and search visibility, but they introduce additional concerns around hydration, duplicate data requests, and differing server and client validation behaviour.

Schema-driven development is also becoming more common. A shared or generated contract can align form types, API payloads, and error structures. It should be governed carefully: sharing code does not eliminate the need for independent server checks, and overly complex shared schemas can couple release cycles.

Useful practices include:

  • Design URLs around resources and tasks, not component names.
  • Provide explicit not-found, unauthorized, forbidden, loading, and failure states.
  • Use lazy loading for coherent, infrequently needed feature areas.
  • Prefer declarative navigation for ordinary links and programmatic navigation for completed actions.
  • Preserve form input after recoverable errors.
  • Validate on the client for guidance and on the server for authority.
  • Map server errors to fields using a stable error contract.
  • Use native HTML semantics before adding ARIA.
  • Test keyboard navigation, screen-reader announcements, refreshes, deep links, back navigation, and slow networks.
  • Instrument route failures and form abandonment without collecting unnecessary sensitive data.

Chapter Summary

Routing gives an application a navigable, bookmarkable structure, while navigation connects views to user tasks and data operations. React commonly assembles routing through libraries such as React Router, whereas Angular provides an integrated router with guards, resolvers, child routes, and lazy loading. Both frameworks support nested, parameterised, protected, and data-aware workflows, but neither client-side routing nor route guards replaces backend authorization.

Forms should be understood as stateful workflows involving values, interaction status, validity, submission, server errors, and navigation. React controlled or library-managed forms provide explicit state control. Angular template-driven forms suit simple cases, while reactive forms are stronger for dynamic and complex workflows. Validation should occur at multiple layers, with the server as the final authority. Accessible labels, focus management, error association, recoverable failures, and predictable navigation are essential elements of professional quality.

Key Terms

Route: A rule that maps a URL or route state to a frontend view and its behaviour.

Nested route: A child route rendered within the layout or outlet of a parent route.

Route parameter: A dynamic URL segment used to identify a resource.

Query parameter: Optional URL data commonly used for filtering, sorting, searching, or pagination.

Protected route: A route whose access depends on authentication or authorization conditions.

Route guard: Logic that permits, denies, or redirects a navigation attempt.

Resolver: A mechanism that obtains required data before route activation.

Lazy loading: Loading a feature or code bundle only when it is needed.

Deep link: A URL that directly opens a particular nested application state.

Controlled form: A form whose input values are managed explicitly by application state.

Reactive form: An Angular form whose controls and validation are defined primarily in TypeScript.

Template-driven form: An Angular form whose model and validation are expressed largely through template directives.

Schema validation: Validation based on a formal reusable description of data structure and rules.

Synchronous validator: A validator that produces a result immediately from current values.

Asynchronous validator: A validator that obtains its result through a delayed operation such as an API request.

Touched state: Information indicating that a user has interacted with or left a form control.

Error boundary: A component-level mechanism for presenting failures without collapsing the entire application.

Authorization: The decision about which actions or resources an authenticated user may access.

ARIA: Accessibility attributes used to communicate interface semantics and state to assistive technologies.

Review Questions

Short Answer Questions

1. Why must a single-page application server be configured to support deep links?

2. Distinguish authentication from authorization in route protection.

3. What is the purpose of a nested route?

4. When is a group-level validator useful?

5. Why is client-side validation insufficient for security?

Long Answer Questions

1. Compare history-based routing and hash routing, including their deployment implications.

2. Explain how React and Angular support protected and lazy-loaded routes.

3. Compare Angular reactive forms with template-driven forms and recommend situations for each.

4. Describe a complete strategy for handling client, server, and asynchronous validation.

5. Explain how accessibility principles should influence error handling and form workflow design.

Analytical / Application Questions

1. Design a route hierarchy for a learning platform containing public courses, authenticated learner dashboards, course lessons, and instructor administration. Identify nested, protected, and lazy-loaded areas.

2. A payment form submits successfully according to the server, but the browser loses connection before receiving the response. What states should the interface represent, and how should duplicate submission be prevented or handled?

3. An Angular approval form contains a start date, end date, approver permission, and optional comments. Propose a reactive-form validation and navigation strategy that handles cross-field errors, asynchronous permission checking, server rejection, and accessible feedback.

22. Multiple Choice Questions with Answers

Question 1

A user refreshes the URL /app/projects/42 and receives a server 404, although navigation to that URL works inside the SPA. What is the most likely missing configuration?

A. A larger client-side form state

B. A server fallback that returns the SPA entry document for frontend routes

C. An additional database index

D. A second authentication token

Correct Answer: B. A server fallback that returns the SPA entry document for frontend routes

Question 2

Which statement best describes the security role of a client-side route guard?

A. It permanently prevents unauthorized API requests

B. It replaces server-side authorization

C. It improves navigation experience but must be supplemented by backend authorization

D. It encrypts route parameters

Correct Answer: C. It improves navigation experience but must be supplemented by backend authorization

Question 3

A route displays a shared project layout while changing only the project detail view inside it. Which routing design is most appropriate?

A. A nested child route rendered through a route outlet

B. A separate browser tab for every project

C. A hash fragment containing the entire project object

D. A global form reset

Correct Answer: A. A nested child route rendered through a route outlet

Question 4

Which situation most strongly justifies lazy loading a feature?

A. The feature is required on the initial landing screen

B. The feature is a large, infrequently used administration area

C. The feature contains a single text input

D. The feature must always be available before login

Correct Answer: B. The feature is a large, infrequently used administration area

Question 5

Why should navigation to a success page normally occur only after a mutation is confirmed successful?

A. It prevents users from losing access to browser history

B. It avoids representing an unconfirmed operation as completed

C. It makes all forms template-driven

D. It eliminates the need for server validation

Correct Answer: B. It avoids representing an unconfirmed operation as completed

Question 6

Which Angular form approach is generally most suitable for a dynamic multi-step form with conditional fields and extensive validation?

A. Reactive forms

B. A static image of the form

C. Only HTML placeholders

D. A route fragment without controls

Correct Answer: A. Reactive forms

Question 7

What is the correct relationship between client-side schema validation and server-side validation?

A. The client schema makes server validation unnecessary

B. The server is authoritative, while client validation provides immediate guidance

C. Only the browser should validate data

D. Schema validation should be used only for navigation

Correct Answer: B. The server is authoritative, while client validation provides immediate guidance

Question 8

A username availability request returns after the user has already typed a different username. What is the principal risk?

A. The URL becomes a deep link

B. A stale asynchronous result may overwrite the current validation state

C. The form automatically becomes accessible

D. The route becomes lazy-loaded

Correct Answer: B. A stale asynchronous result may overwrite the current validation state

Question 9

Which practice best supports accessible form error handling?

A. Use placeholder text instead of labels

B. Show errors only through colour

C. Associate errors with controls and provide an accessible summary

D. Disable the submit button without explanation

Correct Answer: C. Associate errors with controls and provide an accessible summary

Question 10

Why should sensitive information generally not be placed in query parameters?

A. Query parameters cannot represent text

B. They may appear in browser history, logs, analytics, or referrer data

C. They prevent nested routes from working

D. They automatically disable lazy loading

Correct Answer: B. They may appear in browser history, logs, analytics, or referrer data

Advanced Frontend Frameworks (React.js, Angular)
Chapter 6 of 15