Container and presentational components help organize your app by separating logic from display. This makes your code easier to understand and maintain.
Container and presentational components in Angular
Start learning this pattern below
Jump into concepts and practice - no test required
/* Presentational component */ import { Component, Input } from '@angular/core'; @Component({ selector: 'app-user-card', standalone: true, template: ` <div> <h2>{{ user.name }}</h2> <p>{{ user.email }}</p> </div> ` }) export class UserCardComponent { @Input() user!: { name: string; email: string }; } /* Container component */ import { Component } from '@angular/core'; import { UserCardComponent } from './user-card.component'; @Component({ selector: 'app-user-container', standalone: true, imports: [UserCardComponent], template: ` <app-user-card [user]="user"></app-user-card> ` }) export class UserContainerComponent { user = { name: 'Alice', email: 'alice@example.com' }; }
Presentational components focus on how things look and receive data via @Input().
Container components handle data and logic, then pass data down to presentational components.
import { Component, Output, EventEmitter } from '@angular/core'; /* Presentational component with event output */ @Component({ selector: 'app-button', standalone: true, template: `<button (click)="clicked.emit()">Click me</button>` }) export class ButtonComponent { @Output() clicked = new EventEmitter<void>(); }
import { Component } from '@angular/core'; import { ButtonComponent } from './button.component'; /* Container listens to presentational event */ @Component({ selector: 'app-button-container', standalone: true, imports: [ButtonComponent], template: `<app-button (clicked)="onClick()"></app-button>` }) export class ButtonContainerComponent { onClick() { alert('Button clicked!'); } }
This example shows a presentational component MessageComponent that only displays a message and a clear button. The container MessageContainerComponent manages the message state and passes it down. It also handles showing and clearing the message.
Accessibility is included with role="alert" and aria-live="polite" so screen readers announce message changes.
import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-message', standalone: true, template: ` <div role="alert" aria-live="polite"> <p>{{ message }}</p> <button (click)="clear.emit()" aria-label="Clear message">Clear</button> </div> ` }) export class MessageComponent { @Input() message = ''; @Output() clear = new EventEmitter<void>(); } @Component({ selector: 'app-message-container', standalone: true, imports: [MessageComponent], template: ` <app-message [message]="currentMessage" (clear)="clearMessage()"></app-message> <button (click)="showMessage()">Show Message</button> ` }) export class MessageContainerComponent { currentMessage = ''; showMessage() { this.currentMessage = 'Hello from container!'; } clearMessage() { this.currentMessage = ''; } }
Use @Input() to pass data from container to presentational components.
Use @Output() with EventEmitter to send events from presentational to container.
Keep presentational components simple and reusable by avoiding data fetching or complex logic inside them.
Container components handle data and logic.
Presentational components focus on displaying data and UI.
This separation makes your app easier to maintain and test.
Practice
Solution
Step 1: Understand container component responsibility
Container components are designed to manage data and logic, such as fetching data or handling user actions.Step 2: Differentiate from presentational components
Presentational components focus on showing data and UI, not on data handling.Final Answer:
To handle data fetching and business logic -> Option DQuick Check:
Container = data and logic [OK]
- Confusing container with presentational component roles
- Thinking container manages styles or routes
- Assuming container handles only UI display
Solution
Step 1: Identify Angular data flow syntax
Data flows from container to presentational via@Input()properties.Step 2: Understand binding in container template
The container passes data by binding to the presentational component's input property in its template.Final Answer:
@Input() data: any; in presentational, bind in container template -> Option BQuick Check:
Data down via @Input() [OK]
- Using @Output() to pass data down instead of events up
- Trying to modify child variables directly
- Confusing ngModel with input binding
<app-user-list [users]="userArray" (selectUser)="onUserSelect($event)"></app-user-list>
What is the role of
(selectUser) here?Solution
Step 1: Recognize Angular event binding syntax
Parentheses aroundselectUserindicate event binding from child to parent.Step 2: Understand event emission from presentational component
The presentational component emitsselectUserevent, container listens and runsonUserSelect.Final Answer:
It listens to an event emitted by the presentational component -> Option CQuick Check:
Parent listens to child event with (event) [OK]
- Thinking (selectUser) passes data down
- Confusing event binding with property binding
- Assuming it styles or initializes state
@Component({
selector: 'app-item',
template: `<div>{{item.name}}</div>`
})
export class ItemComponent {
item: any;
}Solution
Step 1: Check data input declaration
The presentational component expects data from parent, soitemmust be decorated with@Input()to receive it.Step 2: Verify template and selector
Template syntax and selector are valid; no abstract class needed.Final Answer:
Missing @Input() decorator on item property -> Option AQuick Check:
@Input() needed to receive data [OK]
- Forgetting @Input() on input properties
- Thinking template interpolation is wrong
- Assuming selector must be different
Solution
Step 1: Identify container responsibility
The container should handle fetching data and managing state.Step 2: Identify presentational responsibility
The presentational component should only display data received via@Input()without fetching or managing state.Final Answer:
Container fetches products, stores in a variable, passes via @Input(); presentational only displays list -> Option AQuick Check:
Container = data fetch, Presentational = display [OK]
- Letting presentational fetch data
- Duplicating data fetch in both components
- Mixing data logic inside presentational
