CodeForge

Angular 21 de Cero a Experto / Fundamentos

input() y output(): datos que bajan, eventos que suben

Teoría22 min20 XP

Un componente aislado no sirve de mucho: la magia está en que las piezas se comuniquen. En React esto era "props que bajan, callbacks que suben"; en Angular es exactamente el mismo patrón con otra sintaxis: input() para recibir datos del padre y output() para avisarle de eventos. Y aquí, por primera vez, aparecen las señales —el sistema reactivo que domina Angular 21—. Hoy conectas componentes al estilo moderno y das el primer paso hacia signals.

El mismo patrón que ya conoces

input(): recibir datos del padre

En Angular 21, un input se declara llamando a la función input(). Devuelve una SEÑAL: una propiedad que lees llamándola como función, this.valor():

tarjeta-gasto.component.ts
import { Component, input } from '@angular/core';

@Component({
selector: 'app-tarjeta-gasto',
template: `
  <article>
    <h3>{{ nombre() }}</h3>          <!-- se lee llamándola: nombre() -->
    <span>{{ valor() }} COP</span>
  </article>
`,
})
export class TarjetaGastoComponent {
nombre = input.required<string>();   // obligatorio: el padre DEBE pasarlo
valor = input(0);                    // opcional con valor por defecto 0
}

El padre le pasa los datos con property binding —los corchetes que ya conoces—:

lista.component.ts
@Component({
imports: [TarjetaGastoComponent],
template: `
  <app-tarjeta-gasto [nombre]="'Café'" [valor]="4500" />
  <app-tarjeta-gasto [nombre]="'Almuerzo'" [valor]="18000" />
`,
})
export class ListaComponent {}

Es idéntico conceptualmente a las props de React: <TarjetaGasto nombre="Café" valor={4500} />. La diferencia clave es que en Angular 21 un input es una SEÑAL de solo lectura: se lee con (), y cuando el padre cambia el valor, todo lo que dependa de esa señal se actualiza solo. Eso es lo que verás a fondo en el próximo módulo.

output(): avisar al padre

Para que el hijo notifique un evento al padre, se usa output(), que devuelve un emisor con un método emit():

tarjeta-gasto.component.ts
import { Component, input, output } from '@angular/core';

@Component({
selector: 'app-tarjeta-gasto',
template: `
  <article>
    <h3>{{ nombre() }}</h3>
    <button (click)="borrar.emit(id())">Eliminar</button>
  </article>
`,
})
export class TarjetaGastoComponent {
id = input.required<number>();
nombre = input.required<string>();
borrar = output<number>();         // emitirá un número (el id a borrar)
}

Y el padre escucha ese evento con event binding —los paréntesis— y recibe el dato en $event:

lista.component.ts
@Component({
imports: [TarjetaGastoComponent],
template: `
  @for (g of gastos; track g.id) {
    <app-tarjeta-gasto
      [id]="g.id"
      [nombre]="g.nombre"
      (borrar)="eliminarGasto($event)" />
  }
`,
})
export class ListaComponent {
gastos = [{ id: 1, nombre: 'Café' }, { id: 2, nombre: 'Almuerzo' }];

eliminarGasto(id: number) {
  this.gastos = this.gastos.filter((g) => g.id !== id);
}
}

Fíjate en el paralelo perfecto con React: allá el hijo recibía una prop onBorrar y la llamaba (onBorrar(id)); aquí declara un output() y hace borrar.emit(id). El padre, que en React pasaba la función, en Angular escucha con (borrar)="...". Mismo flujo, misma dirección: datos abajo, eventos arriba.

model(): two-way binding en un componente

Cuando quieres que un dato viaje en AMBAS direcciones entre padre e hijo (el hijo puede leerlo y cambiarlo, y el padre se entera), usas model(). Es un input y un output combinados —la "banana en caja" a nivel de componente—:

campo-monto.component.ts
import { Component, model } from '@angular/core';

@Component({
selector: 'app-campo-monto',
template: `<input type="number" [value]="monto()" (input)="onInput($event)" />`,
})
export class CampoMontoComponent {
monto = model(0);   // two-way: el padre usa [(monto)]="..."

onInput(e: Event) {
  this.monto.set(Number((e.target as HTMLInputElement).value));
}
}
// El padre lo usa así:  <app-campo-monto [(monto)]="totalGasto" />

Antes (@Input/@Output decoradores) vs Ahora (input()/output())

En Angular 21, un input declarado con `input()` es…

Mini-reto

Diseña un TarjetaGastoComponent: 1) recibe nombre (obligatorio) y valor con input(); 2) muestra ambos en el template leyéndolos con (); 3) tiene un botón que emite un output() llamado editar con el id del gasto. Luego escribe cómo lo usaría el padre dentro de un @for, pasando los inputs con [ ] y escuchando el output con ( ).

Qué sigue

Cerraste el módulo de fundamentos: sabes crear componentes, enlazar datos, decidir, repetir y comunicar piezas. Y viste asomar las señales en input(). El próximo módulo es el corazón de Angular 21 —signals a fondo—: qué son, cómo se derivan con computed, cómo reaccionan con effect, y por qué reemplazaron a zone.js. Antes, un reto para afianzar todo lo de este módulo.