Skip to content
advanced Phase 16 · Enterprise Patterns and Capstone

Facades and Smart/Dumb Components

Separate data-fetching containers from presentational views.

45m
0 problems
Topic Progress 0%

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

  1. Simplified components — No direct store or HTTP access
  2. Centralized state access — All state reads go through the facade
  3. Easy to test — Mock the facade instead of the store
  4. Refactoring freedom — Change store implementation without touching components

Quiz

1. What is a smart component?

Question 1 options

2. What is a dumb component?

Question 2 options

3. What is a facade service?

Question 3 options

Flashcards

Question

What is a smart component?

Answer

Container component that fetches data and manages state.

Question

What is a dumb component?

Answer

Presentational component: @Input data, @Output events. No state.

Question

What is a facade service?

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()); }
}

Continue Learning