Angular Architecture: The Power of Smart & Dumb Components
A clean Angular architecture is not built on complex enterprise patterns. It is built on simple principles applied consistently.
One of the most powerful principles you can adopt is separating your components into two distinct categories: “Smart” and “Dumb.” This pattern instantly leads to code that is easier to test, reuse, and maintain.
What is a “Smart” Component?
Smart components, also known as Container components, act as the brain of a specific feature. They handle the business logic and state management.
Their responsibilities include:
- Injecting services (like
HttpClientor NgRx Store). - Fetching and mutating data.
- Passing data down to child components.
- Reacting to events emitted by child components.
A Smart component rarely contains complex HTML or CSS. Its template is usually just a wrapper that delegates the actual rendering to Dumb components.
@Component({
selector: 'app-user-dashboard',
template: `
<app-user-card
[user]="user$ | async"
(saveClicked)="onSaveUser($event)">
</app-user-card>
`
})
export class UserDashboardComponent {
user$ = this.userService.getCurrentUser();
constructor(private userService: UserService) {}
onSaveUser(userData: User) {
this.userService.updateUser(userData).subscribe();
}
}
What is a “Dumb” Component?
Dumb components, or Presentational components, are entirely responsible for the user interface. They do not know where their data comes from or what happens when a button is clicked.
Their rules are strict:
- They only receive data via
@Input(). - They only communicate actions outwards via
@Output()EventEmitters. - They have no dependencies on external services. They do not inject services in their constructor.
@Component({
selector: 'app-user-card',
template: `
<div class="card">
<h2>{{ user.name }}</h2>
<button (click)="onSave()">Save</button>
</div>
`
})
export class UserCardComponent {
@Input() user!: User;
@Output() saveClicked = new EventEmitter<User>();
onSave() {
this.saveClicked.emit(this.user);
}
}
Why This Pattern Matters
By strictly separating data fetching from presentation, you gain significant advantages.
Your Dumb components become highly reusable. You can drop the app-user-card anywhere in your application, pass it a user object, and it will render perfectly.
Testing becomes trivial. To test a Dumb component, you just pass it mock inputs and assert the HTML output. You do not need to mock HTTP interceptors or complex state stores.
Keep your logic separated, and your application will scale smoothly.