Apariencia
🧠 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) usandoinput()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) | |
|---|---|---|
| Responsabilidad | Maneja estado y lógica (llama servicios, guarda datos) | Solo muestra datos y avisa eventos |
| Recibe datos | Los obtiene él mismo (servicio, HTTP, etc.) | Por @Input(), se los pasa el padre |
| Emite acciones | Reacciona directamente | Por @Output() + EventEmitter, le avisa al padre |
| Reusabilidad | Poco 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, enciclo-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 eneventos-en-angular.mdcomo "Eventos personalizados".
🍎 Ejemplo real: fruta (dumb) + lista-frutas (smart)
🗺️ Diagrama: flujo de datos entre los dos

- Datos hacia abajo:
ListaFrutas(smart) le pasa cadafrutadel array aFruta(dumb) vía[nombreFruta]="fruta"— uninput()signal. - Eventos hacia arriba (pendiente): todavía no se vio
@Output()en el curso, pero el patrón completo terminaría conFrutaavisando aListaFrutascuando 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 enbienvenida.ts). Como es un signal, en el template se llama como función:{{ nombreFruta() }}, no{{ nombreFruta }}.Frutano 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>ListaFrutasSÍ tiene lógica: decide qué datos existen (el arrayfrutas, inicializado enngOnInit) y arma el@forpara 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().Frutani 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.
Frutase puede probar y reusar sin depender de dónde vienen los datos; si mañanaListaFrutastrae las frutas de una API en vez de un array fijo,Frutano 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 enbienvenida.ts(verciclo-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 queFruta(dumb) le avise algo aListaFrutas(smart) — por ejemplo, que el usuario seleccionó una fruta. Hoy los datos solo viajan hacia abajo (input()); esa mitad quedó pendiente eneventos-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()debienvenidaya es la mitad "dumb → recibe datos" del patrón.
➡️ Siguiente
Esperando el ejemplo real (código/capturas) para completar esta nota.