Skip to content

🧠 Clase 7 — Smart vs Dumb Component

📅 2026-07-18 · 🗂️ Proyecto: AppClase04 (destino en este repo: app-semana04) · 🎯 Smart Component vs Dumb Component

⬅️ Viene después de Clase 4, Clase 5 y Clase 6 (mismo día de clase).

🎯 Qué aprendí

  • Qué es el patrón Smart vs Dumb Component y por qué separa responsabilidades.
  • Cómo se ve un Dumb Component real (Fruta) usando input() signal.
  • Cómo se ve un Smart Component real (ListaFrutas) que arma los datos y se los pasa al dumb con @for + [nombreFruta].
  • Que falta la otra mitad del patrón (@Output()/EventEmitter), pendiente de ver.

🗂️ De qué trata (teoría general)

Es un patrón de organización de componentes, no una sintaxis nueva de Angular: se trata de separar responsabilidades según qué hace cada componente.

Smart Component (Container)Dumb Component (Presentational)
ResponsabilidadManeja estado y lógica (llama servicios, guarda datos)Solo muestra datos y avisa eventos
Recibe datosLos obtiene él mismo (servicio, HTTP, etc.)Por @Input(), se los pasa el padre
Emite accionesReacciona directamentePor @Output() + EventEmitter, le avisa al padre
ReusabilidadPoco reusable (atado a un caso concreto)Muy reusable (no sabe de dónde vienen los datos)

📌 Esto conecta directo con lo que ya vimos: @Input() (bienvenida.ts, en ciclo-de-vida.md) es la mitad del patrón. La otra mitad, @Output() + EventEmitter (el hijo "dumb" avisándole algo al padre "smart"), todavía no la vimos — quedó pendiente en eventos-en-angular.md como "Eventos personalizados".

🍎 Ejemplo real: fruta (dumb) + lista-frutas (smart)

🗺️ Diagrama: flujo de datos entre los dos

Diagrama de flujo de datos entre Smart y Dumb Component en Angular: ListaFrutas (smart) envía nombreFruta hacia Fruta (dumb) mediante input() signal, datos hacia abajo; una futura conexión output() pendiente de @Output() sin ver aún en la clase, iría hacia arriba

  • Datos hacia abajo: ListaFrutas (smart) le pasa cada fruta del array a Fruta (dumb) vía [nombreFruta]="fruta" — un input() signal.
  • Eventos hacia arriba (pendiente): todavía no se vio @Output() en el curso, pero el patrón completo terminaría con Fruta avisando a ListaFrutas cuando el usuario hace algo (p. ej. seleccionar una fruta) — la mitad que falta del patrón smart/dumb.

fruta.ts / fruta.html — el Dumb Component

ts
import { Component, input } from '@angular/core';

@Component({
  selector: 'app-fruta',
  imports: [],
  templateUrl: './fruta.html',
  styleUrl: './fruta.css',
})
export class Fruta {

  nombreFruta = input('');

}
html
<p>{{ nombreFruta() }}</p>
  • input('') — la nueva API de signals para @Input() (equivalente moderno del decorador @Input() que ya usamos en bienvenida.ts). Como es un signal, en el template se llama como función: {{ nombreFruta() }}, no {{ nombreFruta }}.
  • Fruta no tiene lógica propia: no decide qué fruta mostrar, no llama servicios, solo recibe un nombre y lo muestra. Por eso es "dumb"/presentacional — se puede reutilizar en cualquier lado con cualquier dato.

📝 Errata real al armar esto: se importó Input (el decorador clásico) pero se usó input('') (la función, minúscula) — son cosas distintas de @angular/core. Hay que importar exactamente la que se usa: import { Component, input } from '@angular/core';.

lista-frutas.ts / lista-frutas.html — el Smart Component

ts
import { Component, OnInit } from '@angular/core';
import { Fruta } from '../fruta/fruta';

@Component({
  selector: 'app-lista-frutas',
  imports: [Fruta],
  templateUrl: './lista-frutas.html',
  styleUrl: './lista-frutas.css',
})
export class ListaFrutas implements OnInit {

  frutas: string[] = [];

  ngOnInit(): void {
    this.frutas = ['Mango', 'Fresa', 'Piña', 'Uva'];
  }

}
html
<p>SMART COMPONNENT</p>

<ul>
    @for (fruta of frutas; track fruta) {
        <app-fruta [nombreFruta]="fruta" />
    }
</ul>
  • ListaFrutas SÍ tiene lógica: decide qué datos existen (el array frutas, inicializado en ngOnInit) y arma el @for para recorrerlo. Por eso es "smart" — conoce el origen de los datos (aquí un array fijo; en una app real, normalmente vendría de un servicio/API).
  • <app-fruta [nombreFruta]="fruta" /> — el patrón completo: el smart (ListaFrutas) le pasa a cada dumb (Fruta) un dato concreto por @Input/input(). Fruta ni se entera de que existe un array o un @for — solo recibe un nombre y lo pinta.
  • Sintaxis <app-fruta ... /> (self-closing, sin </app-fruta>) — válida en Angular moderno para componentes sin contenido proyectado (equivalente a <app-fruta ...></app-fruta>).

🧪 Tip de entrevista: ¿Cuál es la ventaja real de separar smart/dumb? → Reusabilidad y testeo. Fruta se puede probar y reusar sin depender de dónde vienen los datos; si mañana ListaFrutas trae las frutas de una API en vez de un array fijo, Fruta no cambia nada.

🏋️ Ejercicios con solución

Ejercicio 1 — un dumb component Precio

Crea un dumb component Precio con un input() signal numérico (valor) que muestre el número formateado como moneda, por ejemplo <p>S/ {{ valor() }}</p>. Debe seguir la misma regla que Fruta: sin lógica propia, solo recibe y muestra.

Ver solución
ts
// precio.ts
import { Component, input } from '@angular/core';

@Component({
  selector: 'app-precio',
  imports: [],
  templateUrl: './precio.html',
  styleUrl: './precio.css',
})
export class Precio {

  valor = input(0);

}
html
<!-- precio.html -->
<p>S/ {{ valor() }}</p>

Uso desde un smart (igual que ListaFrutas con Fruta):

html
<app-precio [valor]="19.90" />

Ejercicio 2 — agregar una segunda propiedad a Fruta

Extiende Fruta para que también reciba un color por input() (además de nombreFruta), y muéstralo en el template. ListaFrutas debe pasar ambos valores.

Ver solución
ts
// fruta.ts
import { Component, input } from '@angular/core';

@Component({
  selector: 'app-fruta',
  imports: [],
  templateUrl: './fruta.html',
  styleUrl: './fruta.css',
})
export class Fruta {

  nombreFruta = input('');
  color = input('');

}
html
<!-- fruta.html -->
<p>{{ nombreFruta() }} ({{ color() }})</p>
html
<!-- lista-frutas.html: ahora habría que pasar también el color, p.ej. con un array de
     objetos { nombre, color } en vez de un array de strings -->
<ul>
    @for (fruta of frutas; track fruta.nombre) {
        <app-fruta [nombreFruta]="fruta.nombre" [color]="fruta.color" />
    }
</ul>

❓ Preguntas y respuestas

1. ¿qué diferencia a un smart de un dumb component?

El smart maneja estado y lógica (de dónde salen los datos: servicio, array, ngOnInit), el dumb solo recibe datos por input y los muestra — no sabe ni le importa de dónde vienen.

2. ¿por qué Fruta usa input() en vez de @Input()?

Son dos formas de declarar lo mismo (recibir un valor del padre); input() es la API nueva basada en signals, y por ser un signal se lee como función en el template ({{ nombreFruta() }}). @Input() (el decorador) es la forma clásica, usada en bienvenida.ts (ver ciclo-de-vida.md).

3. ¿qué le falta a este ejemplo para ser un patrón smart/dumb completo?

Le falta la comunicación hacia arriba: @Output() + EventEmitter, para que Fruta (dumb) le avise algo a ListaFrutas (smart) — por ejemplo, que el usuario seleccionó una fruta. Hoy los datos solo viajan hacia abajo (input()); esa mitad quedó pendiente en eventos-en-angular.md.

❓ Pendiente

  • [ ] Código de @Output() + EventEmitter (comunicación dumb → smart, hijo avisa algo al padre) — todavía no apareció en la clase.
  • [ ] ¿El profe usa estos nombres exactos ("smart"/"dumb"), o también menciona "Container/Presentational" como sinónimo?

📎 Apuntes relacionados

  • eventos-en-angular.md — "Eventos personalizados" (@Output()) quedó pendiente ahí, y es justo la pieza que falta para completar este patrón.
  • ciclo-de-vida.md — el @Input() de bienvenida ya es la mitad "dumb → recibe datos" del patrón.

➡️ Siguiente

Esperando el ejemplo real (código/capturas) para completar esta nota.