Module Overview
Estimated Time: 4-5 hours | Difficulty: Advanced | Prerequisites: Module 11
- Smart vs Presentational components
- Container pattern
- Facade pattern for state management
- Feature modules and domain-driven design
- Dynamic components
- Content projection patterns
Smart vs Presentational Components
Presentational Component
Smart Component
Facade Pattern
Simplify complex state management with facades. The Facade pattern provides a single, unified API that hides the complexity of multiple underlying services. Without a facade, your component might need to inject five different services, coordinate their calls, handle errors from each, and manage loading states — all in one file. With a facade, the component just callsfacade.loadUsers() and reads facade.users(). The facade absorbs all the coordination complexity.
This is especially valuable in large teams: the facade becomes the “contract” between the UI layer and the data layer. Component developers do not need to understand how data is fetched, cached, or updated — they just use the facade’s public API.
Feature Module Structure
Feature Routes
Content Projection
Basic Projection
Conditional Projection with ngTemplateOutlet
Dynamic Components
Control Value Accessor
Create custom form controls that integrate seamlessly with Angular’s forms system. TheControlValueAccessor interface is the bridge between custom UI components and Angular’s FormControl. Once implemented, your custom component works with formControlName, [(ngModel)], validation, and all other form features — it becomes a first-class citizen of the forms API. This is how component libraries like Angular Material implement their input components.
Inheritance vs Composition
Practice Exercise
Exercise: Build a Generic Data Table Component
Create a reusable data table with:
- Generic type support
- Column configuration via input
- Sorting and pagination
- Row selection
- Custom cell templates via content projection
Solution
Solution
Summary
1
Smart/Presentational
Separate stateful containers from pure presentational components
2
Facade Pattern
Simplify complex state with a unified API
3
Feature Modules
Organize by domain with clear boundaries
4
Content Projection
Build flexible, customizable components
5
Composition over Inheritance
Prefer composable patterns for flexibility
Interview Deep-Dive
Q: Explain the Smart/Presentational component pattern. In a real codebase, how strictly do you enforce this separation, and where does it break down?
Q: Explain the Smart/Presentational component pattern. In a real codebase, how strictly do you enforce this separation, and where does it break down?
Strong Answer: Smart components (containers) manage state, inject services, handle business logic, and coordinate child components. Presentational components (dumb) only receive data via inputs, emit events via outputs, and have no knowledge of services or application state. The benefit: presentational components are trivially testable, highly reusable, and easy to reason about because they are pure functions of their inputs.In practice, I enforce this strictly for shared/reusable components — a ButtonComponent, CardComponent, or DataTableComponent should never inject a service. For feature-level components, I am pragmatic. A UserProfileComponent that only appears in one place might inject a service directly rather than wrapping it in a container. The overhead of creating a container just to pass data down one level is not always worth the abstraction.Where it breaks down: deeply nested component trees. If a smart container needs to pass data through five levels of presentational components, you end up with “prop drilling” — every intermediate component has inputs and outputs it does not use, just passes through. The solutions are either a shared service with signals (components subscribe directly), content projection (skip intermediate levels), or a state management library like NgRx/SignalStore.The other breakdown: when a presentational component needs to trigger a complex action (like opening a modal with specific context). You can emit an event, but the parent needs to handle it, which might require injecting a modal service. At some point, the strict separation creates more indirection than clarity.Follow-up: How does this pattern interact with OnPush change detection?
Answer: Beautifully. Presentational components with OnPush only re-render when their input references change. Since smart components pass data down, the presentational layer only updates when the smart component provides new data. This creates a natural performance boundary. The smart component decides when data changes; the presentational component decides how to display it. Combined with signal inputs, this means each presentational component only re-renders when its specific data changes.
Q: How would you design a generic, reusable data table component that supports sorting, pagination, custom cell templates, and row selection -- without becoming an unmaintainable monolith?
Q: How would you design a generic, reusable data table component that supports sorting, pagination, custom cell templates, and row selection -- without becoming an unmaintainable monolith?
Strong Answer: The key architectural decision is separating behavior from rendering. The data table should handle the mechanics (sorting logic, pagination math, selection tracking) but let consumers control how cells are rendered via content projection.I would define a ColumnDef interface with key, header, sortable flag, and optional width. The component accepts data and columns as inputs. For custom cell rendering, I use ng-template with a template outlet pattern — consumers pass named templates that the table renders for specific columns. The default fallback is plain text interpolation of the cell value.For sorting: I maintain sortKey and sortDir signals internally. Clicking a header toggles the sort. The sorted data is a computed signal that derives from the input data and the sort state. For pagination: page and pageSize signals, with a paginatedData computed that slices the sorted data. The total pages is another computed.For row selection: a Set signal tracks selected IDs. The component exposes selectionChange as an output. A “select all” checkbox computes whether all visible rows are selected.The critical design decision is what NOT to include. I would not build in filtering (let consumers pre-filter data before passing it in), server-side sorting (too many assumptions about API shape), or inline editing (that is a different component). Keeping the scope tight makes the component genuinely reusable.Follow-up: How would you handle 50,000 rows in this table?
Answer: I would integrate CDK virtual scrolling. Instead of rendering all rows, cdk-virtual-scroll-viewport only renders the rows visible in the viewport plus a small buffer. The data table still sorts and paginates the full dataset, but only renders a window. This changes the template from @for over paginatedData to cdkVirtualFor. The sorting and pagination computeds still work on the full array, but the DOM only has 20-30 rows at any time. If the data is too large even for client-side sorting (millions of rows), I would switch to server-side sorting and pagination, where the component emits sort/page change events and the parent fetches the appropriate page from the API.
Next Steps
Next: Server-Side Rendering
Learn SSR and hydration for better performance and SEO