Components, Templates, and Reusable UI DesignIntroductionModern 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 ObjectivesBy the end of this chapter, learners should be able to: - Explain the purpose and structure of components and templates.
- Pass data into components using React props and Angular inputs.
- Communicate user actions using callbacks and Angular outputs.
- Apply composition to create flexible and reusable interfaces.
- Describe component lifecycle concepts and use hooks appropriately.
- Explain Angular directives and pipes and identify comparable React techniques.
- Use services and custom hooks to separate reusable logic from presentation.
- Evaluate component designs using cohesion, coupling, accessibility, and maintainability.
- Design a small reusable component system for a realistic application.
1. The Component ModelA 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 componentsA 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 componentsAn 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 BindingA 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 JSXReact 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 templatesAngular provides explicit binding forms: | Binding | Example | Purpose |
|---|
| 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 OutputsReusable 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 callbacksProps 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 outputsAngular 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 interfaceA 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 ConfigurationComposition means constructing a larger interface from smaller components. It is often preferable to creating one highly configurable component with many flags. React compositionReact’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 projectionAngular 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 componentsA 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 ConceptsA 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 hooksReact 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 lifecycleAngular 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 disciplineLifecycle 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 AngularA 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 ConcernsA component should coordinate the interface rather than contain every application rule. Services and custom hooks provide reusable logic. Angular servicesAngular 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 hooksA 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 PrinciplesSingle responsibilityA 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 couplingHigh 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 inputsA 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 designReusable 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 optimizationUnnecessary 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 contractsA 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 CatalogueThe following is an illustrative case study. BackgroundA 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. ProblemAn 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. AnalysisThe page contains several responsibilities: - Retrieve course data.
- Track loading and error states.
- Store filter criteria.
- Derive the visible course list.
- Display each course.
- 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. ApproachA 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. OutcomeThe 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 pointsThe 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 PracticeComponentization 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 ChecklistBefore 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 SummaryComponents 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 TermsComponent: 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 QuestionsShort Answer Questions- What is a component, and what responsibilities does it commonly contain?
- How do React props differ from Angular inputs?
- Why should list items generally use stable keys in React?
- What is the purpose of an Angular pipe?
- Why must subscriptions and event listeners be cleaned up?
Long Answer Questions- Compare React JSX and Angular templates as mechanisms for expressing user interfaces.
- Explain one-way data flow and show how a child component communicates a user action to its parent in React and Angular.
- Discuss composition and explain why it can be preferable to a component with many configuration flags.
- Describe the roles of React hooks and Angular lifecycle hooks in managing state and side effects.
- Explain how cohesion, coupling, accessibility, and testability influence reusable component design.
Analytical / Application Questions- Design a component hierarchy for an online shopping cart. Identify the inputs, outputs, state owners, and reusable services or hooks.
- 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.
- 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 AnswersQuestion 1Which 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 2In 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 3Which 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 4What 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 5Which 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 6What 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 7Which 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 8What 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 9Which 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 10Which 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.
|