Smart and Dumb Components
Smart components fetch data and delegate rendering to presentational dumb components, separating concerns.
Smart Component (Container)
@Component({
selector: 'app-user-list-container',
template: `<app-user-list [users]="users()" (select)="onSelect($event)"></app-user-list>`,
})
export class UserListContainerComponent {
private store = inject(Store);
users = toSignal(this.store.select(selectUsers), { initialValue: [] });
onSelect(user: User): void {
this.store.dispatch(selectUser({ id: user.id }));
}
}
Dumb Component (Presentational)
@Component({
selector: 'app-user-list',
template: `
<div *ngFor="let user of users">
<p>{{ user.name }}</p>
<button (click)="select.emit(user)">Select</button>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserListComponent {
@Input() users: User[] = [];
@Output() select = new EventEmitter<User>();
}
Benefits
| Benefit | Description |
|---|---|
| Testability | Dumb components are easy to test (pure input/output) |
| Reusability | Dumb components can be reused with different data sources |
| Maintainability | Clear separation of concerns |
| Performance | OnPush works naturally with dumb components |
Facade Services
A facade service exposes one API to components while hiding NgRx or HTTP plumbing.
Facade Service
@Injectable({ providedIn: 'root' })
export class UserFacade {
private store = inject(Store);
// State
users = toSignal(this.store.select(selectUsers), { initialValue: [] });
loading = toSignal(this.store.select(selectLoading), { initialValue: false });
error = toSignal(this.store.select(selectError), { initialValue: null });
// Actions
loadUsers(): void {
this.store.dispatch(loadUsers());
}
addUser(user: CreateUserRequest): void {
this.store.dispatch(addUser({ user }));
}
deleteUser(id: number): void {
this.store.dispatch(deleteUser({ id }));
}
selectUser(id: number): void {
this.store.dispatch(selectUser({ id }));
}
}
Component Using Facade
@Component({
selector: 'app-user-page',
template: `
<div *ngIf="facade.loading()">Loading...</div>
<div *ngIf="facade.error() as error">{{ error }}</div>
<app-user-list
[users]="facade.users()"
(select)="facade.selectUser($event.id)">
</app-user-list>
<button (click)="facade.loadUsers()">Refresh</button>
`,
})
export class UserPageComponent {
facade = inject(UserFacade);
}
Facade Benefits
- Simplified components — No direct store or HTTP access
- Centralized state access — All state reads go through the facade
- Easy to test — Mock the facade instead of the store
- Refactoring freedom — Change store implementation without touching components
Quiz
1. What is a smart component?
2. What is a dumb component?
3. What is a facade service?
Flashcards
Question
What is a smart component?
Click to reveal answer
Answer
Container component that fetches data and manages state.
Question
What is a dumb component?
Click to reveal answer
Answer
Presentational component: @Input data, @Output events. No state.
Question
What is a facade service?
Click to reveal answer
Answer
Simplified API over NgRx/HTTP. Hides plumbing from components.
Revision Notes
Key Takeaways
- 1. Smart components fetch data and manage state
- 2. Dumb components are purely presentational
- 3. Facade services hide NgRx/HTTP plumbing
- 4. Separation improves testability and reusability
Interview Tips
- • Explain the smart/dumb component pattern
- • Be ready to describe facade service benefits
- • Mention OnPush with dumb components
Cheat Sheet
Facades and Smart/Dumb Components Cheat Sheet
Smart component:
- Fetches data, manages state
- Delegates rendering to dumb components
Dumb component:
- @Input data, @Output events
- OnPush change detection
- No direct state access
Facade service:
@Injectable({ providedIn: 'root' })
export class UserFacade {
users = toSignal(this.store.select(selectUsers));
loadUsers() { this.store.dispatch(loadUsers()); }
}