Smart & Dumb Components : Architecture scalable

🏷️ Front-end 📅 03/04/2026 09:00:00 👤 Mezgani Said
Angular Architecture Components Pattern
Smart & Dumb Components : Architecture scalable

Maîtrisez le pattern Smart/Dumb components : container vs presentation, data binding, communication parent-enfant et architecture scalable.

Introduction

Le pattern Smart/Dumb Components (aussi appelé Container/Presentational) est une architecture qui sépare la logique métier de la présentation. C'est une excellente façon de créer des applications maintenables, testables et réutilisables.

Idée fondamentale : Les conteneurs gèrent les données et la logique. Les composants de présentation ne font qu'afficher. Responsabilité unique = code plus clair.

Smart Components — Les conteneurs

Smart Components (ou Container Components) orchestrent la logique métier. Ils :

  • ✅ Gèrent la logique métier et l'état
  • ✅ Communiquent avec les services (HTTP, state management)
  • ✅ Passent les données aux Dumb Components via @Input
  • ✅ Écoutent les événements des Dumb Components via @Output
  • ❌ Ne contiennent pas ou très peu de HTML
// user-list.container.ts — Smart Component
import { Component, OnInit } from '@angular/core';
import { UserService } from '../../services/user.service';

@Component({
    selector: 'app-user-list',
    template: `
        <div class="loading" *ngIf="isLoading">Chargement...</div>
        <app-user-list-presentation
            [users]="users"
            [selectedUserId]="selectedUserId"
            (userSelected)="onUserSelected($event)"
            (userDeleted)="onUserDeleted($event)">
        </app-user-list-presentation>
    `
})
export class UserListContainerComponent implements OnInit {
    users: User[] = [];
    selectedUserId: number | null = null;
    isLoading = false;

    constructor(private userService: UserService) {}

    ngOnInit() {
        this.loadUsers();
    }

    private loadUsers() {
        this.isLoading = true;
        this.userService.getUsers().subscribe({
            next: (users) => {
                this.users = users;
                this.isLoading = false;
            },
            error: (err) => {
                console.error('Erreur :', err);
                this.isLoading = false;
            }
        });
    }

    onUserSelected(userId: number) {
        this.selectedUserId = userId;
    }

    onUserDeleted(userId: number) {
        this.userService.deleteUser(userId).subscribe(() => {
            this.users = this.users.filter(u => u.id !== userId);
        });
    }
}

Dumb Components — Les présentationnels

Dumb Components (ou Presentational Components) ne font qu'afficher. Ils reçoivent les données et émettent des événements. Zéro logique métier :

  • ✅ Reçoivent les données via @Input
  • ✅ Émettent les événements utilisateur via @Output
  • ✅ Ne connaissent pas les services (pas d'HttpClient, pas d'injectables)
  • ✅ Sont 100% réutilisables dans d'autres contextes
  • ✅ Faciles à tester (pas de dépendances externes)
// user-list.presentation.ts — Dumb Component
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'app-user-list-presentation',
    template: `
        <div class="user-list">
            <div *ngFor="let user of users"
                 class="user-item"
                 [class.selected]="user.id === selectedUserId"
                 (click)="selectUser(user.id)">
                <h4>{{ user.name }}</h4>
                <p>{{ user.email }}</p>
                <button (click)="deleteUser(user.id); $event.stopPropagation()">
                    Supprimer
                </button>
            </div>
        </div>
    `
})
export class UserListPresentationComponent {
    @Input() users: User[] = [];
    @Input() selectedUserId: number | null = null;
    @Output() userSelected = new EventEmitter<number>();
    @Output() userDeleted = new EventEmitter<number>();

    selectUser(userId: number) {
        this.userSelected.emit(userId);
    }

    deleteUser(userId: number) {
        if (confirm('Êtes-vous sûr ?')) {
            this.userDeleted.emit(userId);
        }
    }
}
Test unitaire d'un Dumb Component :
it('should emit userDeleted when delete button clicked', () => {
    spyOn(component.userDeleted, 'emit');
    component.users = [{ id: 1, name: 'John' }];
    component.deleteUser(1);
    expect(component.userDeleted.emit).toHaveBeenCalledWith(1);
});

Communication Smart ↔ Dumb

Les données circulent selon un flux unidirectionnel :

Direction Mécanisme Exemple
Smart → Dumb @Input() [users]="users"
Dumb → Smart @Output() EventEmitter (userDeleted)="onDelete($event)"
Smart → Service Dependency Injection constructor(private api: ApiService)
Règle d'or : Les données descendent via @Input. Les événements remontent via @Output. Les services ne sont injectés que dans les Smart Components.

Structure de projet recommandée

Organisez vos composants par feature :

src/app/
  features/
    users/                           # Feature module
      ├── containers/                # Smart Components
      │   └── user-list/
      │       ├── user-list.container.ts
      │       └── user-list.container.html
      ├── components/                # Dumb Components
      │   ├── user-card/
      │   │   ├── user-card.component.ts
      │   │   ├── user-card.component.html
      │   │   └── user-card.component.spec.ts
      │   └── user-form/
      ├── services/
      │   ├── user.service.ts
      │   └── user.service.spec.ts
      ├── models/
      │   └── user.model.ts
      └── users.module.ts
Conventions de nommage :
  • *.container.ts → Smart Component
  • *.component.ts → Dumb Component
  • Cela rend l'architecture visible au premier coup d'œil

Conclusion

Smart/Dumb Components crée une architecture claire, maintenable et testable. Les bénéfices :

  • Clarté : Qui gère la logique ? Qui affiche ? C'est explicite
  • Réutilisabilité : Les Dumb Components s'utilisent partout
  • Testabilité : Pas de dépendances = tests plus simples
  • Performance : Les Dumb Components changent peu = moins de détection
  • Équipes : Une équipe gère la logique, l'autre la présentation
Alternative moderne : Avec Angular 17+ Signals et reactive pattern, certains préfèrent un approche plus fluide sans distinction stricte Smart/Dumb. Mais ce pattern reste une excellente fondation.