Frontend Development using React.js or Angular

Chapter 5
Components, Templates, and Reusable UI Design

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

Components, Templates, and Reusable UI Design

Introduction

Modern frontend applications are not built as single pages containing one large block of code. They are assembled from smaller, focused units called components. A component combines a visual structure, presentation rules, and interaction logic. React and Angular both use component-based development, although they express components differently. React commonly uses JavaScript or TypeScript functions that return JSX, while Angular uses TypeScript classes connected to HTML templates and metadata.

Component-based design improves maintainability because developers can change one part of an interface without rewriting the entire application. It also supports consistency: a button, product card, navigation bar, modal dialog, or form field can be implemented once and reused in many locations.

This chapter explains how to design reusable components using props or inputs, events or outputs, composition, lifecycle concepts, directives, pipes, hooks, services, and established design principles. Examples use both React and Angular so that the underlying ideas can be transferred between frameworks.

Learning Objectives

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

  1. Explain the purpose and structure of components and templates.
  2. Pass data into components using React props and Angular inputs.
  3. Communicate user actions using callbacks and Angular outputs.
  4. Apply composition to create flexible and reusable interfaces.
  5. Describe component lifecycle concepts and use hooks appropriately.
  6. Explain Angular directives and pipes and identify comparable React techniques.
  7. Use services and custom hooks to separate reusable logic from presentation.
  8. Evaluate component designs using cohesion, coupling, accessibility, and maintainability.
  9. Design a small reusable component system for a realistic application.

1. The Component Model

A component is a self-contained unit of user-interface behaviour. It normally has four related concerns:

  • Structure: the elements displayed to the user.
  • Presentation: styles, layout, and visual states.
  • Data: values received from a parent or obtained from another source.
  • Behaviour: event handling, validation, state changes, and side effects.

A useful conceptual model is:

Component = template or render function + data + behaviour + styling

The component should expose a clear public interface while hiding implementation details. For example, a ProductCard may accept a product and an onAddToCart action. The parent should not need to know how the card formats the price or renders its internal button.

React components

A React function component receives a props object and returns JSX:

interface ProductCardProps {
  name: string;
  price: number;
  onAdd: () => void;
}

function ProductCard({ name, price, onAdd }: ProductCardProps) {
  return (
    <article className="product-card">
      <h2>{name}</h2>
      <p>${price.toFixed(2)}</p>
      <button onClick={onAdd}>Add to cart</button>
    </article>
  );
}

JSX resembles HTML, but it is JavaScript syntax transformed into calls that create React elements. JavaScript expressions are placed inside braces. Component names begin with an uppercase letter so that React distinguishes them from native HTML elements.

Angular components

An Angular component usually consists of a TypeScript class, an HTML template, and metadata supplied by the @Component decorator:

@Component({
  selector: 'app-product-card',
  template: `
    <article class="product-card">
      <h2>{{ name }}</h2>
      <p>{{ price | currency }}</p>
      <button type="button" (click)="add.emit()">Add to cart</button>
    </article>
  `
})
export class ProductCardComponent {
  @Input() name = '';
  @Input() price = 0;
  @Output() add = new EventEmitter<void>();
}

Angular templates use interpolation, property binding, event binding, directives, and pipes. The component class contains state and methods, while the template expresses how that state is displayed.

2. Templates and Data Binding

A template is the declarative description of a component’s user interface. Declarative code states what should be displayed rather than manually describing every DOM operation.

React JSX

React supports several common forms of binding:

function Greeting({ userName }: { userName: string }) {
  const message = userName ? `Welcome, ${userName}` : 'Welcome, guest';
  return <h1>{message}</h1>;
}

Conditional rendering can use JavaScript expressions:

{isLoading && <Spinner />}
{error ? <ErrorMessage message={error} /> : <Results items={items} />}

Collections should be rendered with stable keys:

<ul>
  {tasks.map(task => <li key={task.id}>{task.title}</li>)}
</ul>

A key identifies an item across renders. It should normally come from a stable domain identifier, not an array index, especially when items can be inserted, deleted, or reordered.

Angular templates

Angular provides explicit binding forms:

BindingExamplePurpose
Interpolation{{ title }}Displays a value as text
Property binding[disabled]="isSaving"Sets a DOM or component property
Event binding(click)="save()"Responds to an event
Two-way binding[(ngModel)]="name"Synchronizes a value in both directions
Structural control@if (visible) { ... }Conditionally creates template content

Angular’s newer control-flow syntax includes @if and @for; applications may also contain the older ngIf and ngFor forms. Teams should follow the conventions and Angular version used by their project.

3. Props, Inputs, Events, and Outputs

Reusable components need a public communication contract. The most important principle is one-way data flow: data generally moves from a parent to a child, while actions move back through an explicitly provided callback or event.

React props and callbacks

Props are read-only inputs supplied by a parent:

function SearchBox({ value, onChange, onSubmit }: {
  value: string;
  onChange: (value: string) => void;
  onSubmit: () => void;
}) {
  return (
    <form onSubmit={event => { event.preventDefault(); onSubmit(); }}>
      <input value={value} onChange={event => onChange(event.target.value)} />
      <button type="submit">Search</button>
    </form>
  );
}

The child does not directly modify the parent’s state. It invokes onChange, allowing the parent to decide how the value is stored or validated.

Angular inputs and outputs

Angular inputs receive values and outputs emit events:

@Input() quantity = 1;
@Output() quantityChange = new EventEmitter<number>();

increase(): void {
  this.quantityChange.emit(this.quantity + 1);
}

The parent may connect these members as follows:

<app-quantity-picker
  [quantity]="cartQuantity"
  (quantityChange)="cartQuantity = $event">
</app-quantity-picker>

$event represents the value emitted by the child. The output should communicate an event or requested change; it should not secretly alter unrelated application state.

Designing a good public interface

A component interface should be:

  • Small: expose only what consumers need.
  • Explicit: use descriptive names and meaningful types.
  • Predictable: preserve consistent behaviour across contexts.
  • Accessible: expose labels, keyboard behaviour, and useful states.
  • Stable: avoid unnecessary breaking changes.

Passing a large configuration object can sometimes reduce prop count, but it may hide dependencies. Conversely, passing twenty individual properties can make usage difficult. The appropriate choice depends on cohesion and expected variation.

4. Composition Rather Than Excessive Configuration

Composition means constructing a larger interface from smaller components. It is often preferable to creating one highly configurable component with many flags.

React composition

React’s children prop allows a component to provide layout while the parent supplies content:

function Panel({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <section className="panel">
      <h2>{title}</h2>
      <div className="panel-body">{children}</div>
    </section>
  );
}

<Panel title="Account details">
  <AccountForm />
</Panel>

This is more flexible than adding Boolean properties such as showForm, showTable, and showSummary to the same component.

Angular content projection

Angular supports composition through content projection:

<app-panel title="Account details">
  <app-account-form></app-account-form>
</app-panel>

The panel template can contain <ng-content></ng-content> where projected content should appear. Selective projection can use CSS-like selectors for headers, actions, or body regions.

Compound components

A compound component system consists of related components that work together, such as Tabs, TabList, Tab, and TabPanel. This approach creates expressive markup while centralizing coordination logic. It is useful for menus, dialogs, tables, and form groups, but it should not be introduced merely to create more files.

5. State, Effects, and Lifecycle Concepts

A component’s state is data that can change during its lifetime and affect rendering. Examples include whether a menu is open, the current form value, or the selected tab. State should be stored at the lowest common ancestor that needs to coordinate it. Storing it too low prevents coordination; storing it too high creates unnecessary complexity.

React hooks

React hooks allow function components to use state and lifecycle-related behaviour:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(current => current + 1)}>
      Count: {count}
    </button>
  );
}

The functional updater is useful when the next state depends on the previous state. Hooks must be called at the top level of a component or custom hook, not inside loops or conditions.

useEffect is intended for synchronizing with external systems, such as browser APIs, subscriptions, or network requests:

useEffect(() => {
  const handleOnline = () => setOnline(true);
  window.addEventListener('online', handleOnline);
  return () => window.removeEventListener('online', handleOnline);
}, []);

The returned function is cleanup. An empty dependency array means the effect is established after the initial mount and cleaned up when the component unmounts, subject to development-mode behaviours of the React version and configuration. Effects should not be used for ordinary calculations that can be performed during rendering.

Other common hooks include useMemo for selectively memoizing a calculation, useCallback for stabilizing a function reference, and useRef for retaining a mutable value or referencing a DOM element. These are optimization or integration tools, not substitutes for clear component design.

Angular lifecycle

Angular components have lifecycle hooks associated with creation, input changes, view initialization, checking, and destruction. Common hooks include:

  • ngOnChanges: responds to changes in input values.
  • ngOnInit: performs initialization after inputs are set.
  • ngAfterViewInit: runs after the component’s view has been initialized.
  • ngOnDestroy: releases subscriptions, timers, and other resources.

A typical cleanup pattern is:

private destroyRef = inject(DestroyRef);

ngOnInit(): void {
  this.service.items$
    .pipe(takeUntilDestroyed(this.destroyRef))
    .subscribe(items => this.items = items);
}

Modern Angular also provides signals and utilities that can reduce manual subscription management. The exact approach should match the Angular version and project standards.

Lifecycle discipline

Lifecycle code should be minimal and purposeful. Network access, timers, event listeners, and subscriptions are side effects and require cleanup. Failure to clean them up can cause memory leaks, duplicate events, stale data, or updates after a view is no longer relevant.

6. Directives and Pipes in Angular

A directive changes the behaviour or appearance of a DOM element or controls template structure.

  • Attribute directives modify an existing element, such as applying a highlight.
  • Structural directives add, remove, or repeat template content.
  • Component directives are directives with their own template.

A simple attribute directive might respond to pointer events and change a host element’s styling. A structural directive can display a placeholder while data is loading. In current Angular projects, built-in control-flow blocks may replace older structural syntax, but the conceptual distinction remains important.

A pipe transforms data for display:

<p>{{ total | currency:'USD' }}</p>
<p>{{ createdAt | date:'mediumDate' }}</p>

Pipes should normally be presentation transformations. Business rules that affect storage, authorization, or financial calculations should not be hidden in a display pipe.

React has no direct equivalent to Angular pipes. Developers commonly use formatting functions, dedicated presentation components, or libraries:

function Price({ amount }: { amount: number }) {
  return <span>{new Intl.NumberFormat('en-US', {
    style: 'currency', currency: 'USD'
  }).format(amount)}</span>;
}

Formatting should respect locale, currency, precision, and accessibility requirements. A displayed monetary value is not necessarily an appropriate value for computation; the application should retain a reliable numeric or decimal representation separately.

7. Services, Custom Hooks, and Separation of Concerns

A component should coordinate the interface rather than contain every application rule. Services and custom hooks provide reusable logic.

Angular services

Angular services are injectable classes often used for HTTP access, authentication, caching, logging, or shared business operations:

@Injectable({ providedIn: 'root' })
export class ProductService {
  constructor(private http: HttpClient) {}

  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>('/api/products');
  }
}

Dependency injection supplies the service to components and supports testing through replacement or mocking. A service should have a coherent responsibility; a single “God service” containing unrelated features becomes difficult to maintain.

React custom hooks

A custom hook packages reusable stateful behaviour:

function useProducts() {
  const [products, setProducts] = useState<Product[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/products')
      .then(response => response.json())
      .then(setProducts)
      .finally(() => setLoading(false));
  }, []);

  return { products, loading };
}

A production implementation should also handle HTTP errors, cancellation, authentication, response validation, and caching as appropriate. Custom hooks are not visual components; they return behaviour and data for components to consume.

8. Component Design Principles

Single responsibility

A component should have one primary reason to change. A product page may reasonably be composed of a toolbar, filter panel, result list, and product card rather than one component performing all tasks.

High cohesion and low coupling

High cohesion means related responsibilities belong together. Low coupling means a component does not depend unnecessarily on internal details of other components. Passing domain data and callbacks is usually less coupled than allowing a child to reach directly into a parent’s state.

Controlled and uncontrolled inputs

A controlled React input receives its value from state and reports changes through a callback. Angular’s reactive forms provide a structured model for validation and value management. Uncontrolled inputs may be simpler for small forms, but controlled or reactive approaches are generally easier to validate, reset, and test consistently.

Accessibility by design

Reusable components must support keyboard operation, visible focus, semantic HTML, labels, appropriate colour contrast, and screen-reader communication. A custom dialog, for example, requires focus management, an accessible name, Escape-key behaviour where appropriate, and restoration of focus when closed. Accessibility cannot be reliably added at the end of development.

Performance without premature optimization

Unnecessary re-rendering, oversized bundles, unstable list keys, and expensive calculations can harm performance. However, memoization adds complexity and should be justified by measurement. Developers should first establish correct state boundaries, stable keys, efficient data loading, and sensible component structure.

Testing and contracts

A reusable component should be tested according to user-visible behaviour. Tests may verify rendering, keyboard interaction, emitted events, validation messages, loading states, and error states. Implementation-specific tests are more fragile because they fail when internal structure changes without changing behaviour.

9. Applied Case: Reusable Course Catalogue

The following is an illustrative case study.

Background

A training provider is developing a course catalogue using React or Angular. Each course has a title, level, duration, category, price, and enrolment status. The catalogue must support filtering, responsive cards, loading feedback, and an enrolment action.

Problem

An initial implementation places fetching, filtering, formatting, and card rendering in one page component. The result is difficult to test. A second page needs the same course card, but changing its layout risks breaking the catalogue.

Analysis

The page contains several responsibilities:

  1. Retrieve course data.
  2. Track loading and error states.
  3. Store filter criteria.
  4. Derive the visible course list.
  5. Display each course.
  6. Request enrolment when a user acts.

The course card should not fetch the entire catalogue or own global enrolment state. It should receive a course and emit an action.

Approach

A suitable design is:

  • CourseCataloguePage: coordinates data and filters.
  • CourseFilter: receives filter values and reports changes.
  • CourseGrid: renders a collection and handles empty states.
  • CourseCard: presents one course and emits enrol.
  • Price: formats currency consistently.
  • CourseService in Angular or useCourses in React: manages retrieval logic.

A React card might be used as follows:

<CourseCard
  course={course}
  onEnroll={() => openEnrollment(course.id)}
/>

An Angular parent might use:

<app-course-card
  [course]="course"
  (enroll)="openEnrollment($event)">
</app-course-card>

The card should display an appropriate disabled or unavailable state, use a real button, and expose the course title as a meaningful heading. The page should distinguish loading, error, empty-result, and successful-result states rather than showing a blank area.

Outcome

The same card can be reused on search, recommendations, and learner-dashboard pages. Tests can verify the card independently from the HTTP service. A change to currency formatting can be made in one presentation utility or pipe. The design also makes later accessibility and responsive improvements more manageable.

Learning points

The central lesson is not simply to create many small files. Good reuse results from clear boundaries, stable contracts, composition, and separation between presentation, state coordination, and data access.

10. Challenges, Limitations, and Contemporary Practice

Componentization can be overused. Very small components may obscure the flow of an application and increase navigation between files. Reuse is valuable only when the abstraction is stable and understandable. Developers should avoid extracting a component merely because a block of markup is long; extraction should improve responsibility, reuse, testing, or readability.

Framework conventions also evolve. React applications increasingly use server rendering, framework-managed data loading, and streaming interfaces. Angular applications increasingly use standalone components, signals, modern control flow, and more explicit dependency management. These developments change implementation techniques but do not remove the underlying principles of clear interfaces, predictable data flow, cleanup, accessibility, and testability.

AI-assisted coding can generate component scaffolding, tests, and refactoring suggestions. It should not be trusted blindly. Generated code must be reviewed for insecure data handling, incorrect lifecycle cleanup, inaccessible markup, excessive re-rendering, licensing concerns, and framework-version compatibility. Teams remain responsible for the behaviour of code introduced with AI assistance.

Best-Practice Checklist

Before accepting a reusable component, ask:

  • Does it have one clear primary responsibility?
  • Are inputs or props typed and named clearly?
  • Are events or outputs explicit and predictable?
  • Is composition preferable to additional configuration flags?
  • Are loading, error, empty, and disabled states represented?
  • Are effects, subscriptions, timers, and listeners cleaned up?
  • Is the markup semantic and keyboard accessible?
  • Is business logic separated from display formatting?
  • Can the component be tested through user-visible behaviour?
  • Is the abstraction genuinely reusable rather than merely abstract?

Chapter Summary

Components are the primary units of reusable frontend design in React and Angular. They combine structure, presentation, data, and behaviour while exposing a controlled public interface. React commonly uses function components, JSX, props, callbacks, and hooks. Angular uses decorated classes, templates, inputs, outputs, lifecycle hooks, directives, pipes, and injectable services.

One-way data flow provides a predictable communication model: parents pass data down, and children report actions upward. Composition through React children or Angular content projection allows flexible interfaces without excessive configuration flags. Lifecycle mechanisms and hooks are important for synchronization and cleanup, especially when components use subscriptions, timers, browser APIs, or network requests.

Reusable design also depends on high cohesion, low coupling, accessibility, stable keys, clear state ownership, testable behaviour, and sensible separation between presentation and business logic. Services and custom hooks help move reusable logic out of visual components. Finally, abstraction should be purposeful: a component system is successful when it makes change safer, interfaces more consistent, and application behaviour easier to understand.

Key Terms

Component: A self-contained unit combining interface structure and behaviour.

Template: A declarative description of a component’s visual structure.

JSX: JavaScript syntax used by React to describe interface elements.

Props: Read-only values passed into a React component.

Input: A value passed from an Angular parent into a child component.

Callback: A function supplied to a component so it can report an action.

Output: An Angular event mechanism used to communicate from child to parent.

Composition: Building complex interfaces from smaller cooperating components.

Content projection: Angular’s mechanism for placing parent-provided content inside a child template.

State: Changeable data that influences rendering or behaviour.

Hook: A React function that provides state, effects, references, or reusable behaviour.

Lifecycle hook: A framework-defined method or mechanism associated with component stages.

Directive: Angular behaviour that modifies elements or controls template structure.

Pipe: An Angular display transformation applied in a template.

Service: An injectable class that encapsulates reusable application logic or data access.

Custom hook: A reusable React function that packages stateful logic.

Cohesion: The degree to which responsibilities within a component belong together.

Coupling: The degree of dependency between software units.

Controlled input: An input whose value is managed by application state.

Content projection: The insertion of consumer-provided markup into a component’s designated template area.

Review Questions

Short Answer Questions

  1. What is a component, and what responsibilities does it commonly contain?
  2. How do React props differ from Angular inputs?
  3. Why should list items generally use stable keys in React?
  4. What is the purpose of an Angular pipe?
  5. Why must subscriptions and event listeners be cleaned up?

Long Answer Questions

  1. Compare React JSX and Angular templates as mechanisms for expressing user interfaces.
  2. Explain one-way data flow and show how a child component communicates a user action to its parent in React and Angular.
  3. Discuss composition and explain why it can be preferable to a component with many configuration flags.
  4. Describe the roles of React hooks and Angular lifecycle hooks in managing state and side effects.
  5. Explain how cohesion, coupling, accessibility, and testability influence reusable component design.

Analytical / Application Questions

  1. Design a component hierarchy for an online shopping cart. Identify the inputs, outputs, state owners, and reusable services or hooks.
  2. A component fetches data, starts a timer, and registers a browser event listener but does not clean up any of them. Analyse the likely problems and propose a framework-appropriate solution.
  3. A team has created one highly configurable DashboardWidget with fifteen Boolean properties. Evaluate this design and recommend a composition-based alternative, including circumstances in which configuration might still be justified.

Multiple Choice Questions with Answers

Question 1

Which statement best describes one-way data flow in a component-based application?

A. Children directly modify all parent state variables.

B. Data moves from parent to child, while child actions are reported through callbacks or events.

C. Data can move only from a child to a parent.

D. All components must share one global state object.

Correct Answer: B. Data moves from parent to child, while child actions are reported through callbacks or events.

Question 2

In React, what is the primary purpose of a stable key when rendering a list?

A. To encrypt the list item.

B. To apply CSS automatically.

C. To help React identify items across renders.

D. To prevent all component re-renders.

Correct Answer: C. To help React identify items across renders.

Question 3

Which Angular feature is intended primarily for transforming values for display in a template?

A. Service

B. Pipe

C. Output

D. Dependency injector

Correct Answer: B. Pipe

Question 4

What is the main purpose of React useEffect?

A. To declare CSS classes.

B. To synchronize a component with an external system or perform a side effect.

C. To replace every event handler.

D. To define an Angular input.

Correct Answer: B. To synchronize a component with an external system or perform a side effect.

Question 5

Which practice best supports component reuse?

A. Allowing each child to access any parent implementation detail.

B. Using a small, explicit, well-typed public interface.

C. Putting all application logic in every visual component.

D. Replacing semantic HTML with generic containers.

Correct Answer: B. Using a small, explicit, well-typed public interface.

Question 6

What does an Angular output commonly provide?

A. A mechanism for a child to emit an event or value to its parent.

B. A formatting transformation for dates.

C. A service-wide HTTP cache automatically.

D. A replacement for all component templates.

Correct Answer: A. A mechanism for a child to emit an event or value to its parent.

Question 7

Which situation is most appropriate for a service or custom hook?

A. Sharing data-fetching or stateful logic across multiple components.

B. Replacing every heading with a div.

C. Hiding an inaccessible keyboard interaction.

D. Adding an arbitrary Boolean flag to every component.

Correct Answer: A. Sharing data-fetching or stateful logic across multiple components.

Question 8

What is content projection in Angular conceptually similar to in React?

A. Using the children prop to supply content to a component.

B. Using a pipe to format a number.

C. Using a lifecycle hook to destroy a service.

D. Using a key to identify a list item.

Correct Answer: A. Using the children prop to supply content to a component.

Question 9

Which is a likely consequence of failing to clean up a subscription or event listener?

A. Improved accessibility automatically.

B. Duplicate events, stale updates, or memory leaks.

C. Automatic type safety.

D. Guaranteed faster rendering.

Correct Answer: B. Duplicate events, stale updates, or memory leaks.

Question 10

Which design choice most directly reflects high cohesion?

A. A product card independently performs authentication, routing, and database migration.

B. A course card focuses on presenting one course and reporting enrolment actions.

C. Every component depends on every other component’s private fields.

D. A single component contains unrelated application features.

Correct Answer: B. A course card focuses on presenting one course and reporting enrolment actions.

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