Input tradicional con @Input

Intermedio
Angular
Angular
Actualizado: 24/09/2025

Introducción a comunicación padre-hijo

En aplicaciones Angular reales, raramente trabajamos con un único componente aislado. En su lugar, construimos interfaces de usuario complejas dividiendo la funcionalidad en múltiples componentes que cooperan entre sí. Esta arquitectura modular hace que nuestras aplicaciones sean más mantenibles y reutilizables.

La comunicación entre componentes es fundamental para crear aplicaciones Angular efectivas. Los componentes necesitan compartir datos, notificarse sobre eventos y coordinar su comportamiento para ofrecer una experiencia de usuario cohesiva.

Jerarquía de componentes

Angular organiza los componentes en una estructura jerárquica similar a un árbol familiar. Cuando un componente incluye otro componente en su plantilla, establece una relación padre-hijo:

<!-- Componente padre: AppComponent -->
<div class="app-container">
  <app-header></app-header>
  <app-product-list></app-product-list>
  <app-footer></app-footer>
</div>

En este ejemplo, AppComponent actúa como componente padre de HeaderComponent, ProductListComponent y FooterComponent, que son sus componentes hijos.

Patrones de comunicación

Existen varios patrones para la comunicación entre componentes en Angular:

  • Comunicación padre → hijo: El padre envía datos al hijo (usando @Input)
  • Comunicación hijo → padre: El hijo notifica eventos al padre (usando @Output)
  • Comunicación entre hermanos: A través del padre común o servicios compartidos
  • Comunicación global: Usando servicios con estado compartido

Comunicación padre-hijo en detalle

La comunicación padre-hijo es el patrón más común y fundamental. Permite que el componente padre controle y configure el comportamiento de sus componentes hijos enviándoles datos.

Casos de uso típicos:

  • Pasar configuración a componentes reutilizables
  • Enviar datos dinámicos que el hijo debe mostrar
  • Controlar el estado o apariencia de componentes hijos
  • Personalizar el comportamiento de componentes genéricos

Imaginemos un componente ProductCard que debe mostrar información de diferentes productos:

// Componente padre
@Component({
  selector: 'app-product-list',
  template: `
    <div class="product-grid">
      <app-product-card></app-product-card>
      <app-product-card></app-product-card>
      <app-product-card></app-product-card>
    </div>
  `
})
export class ProductListComponent {
  // ¿Cómo le decimos a cada tarjeta qué producto mostrar?
}

Sin comunicación padre-hijo, cada ProductCard sería idéntica y estática. Necesitamos una forma de que el padre envíe datos específicos a cada instancia del componente hijo.

Beneficios del patrón padre-hijo

Este patrón de comunicación ofrece múltiples ventajas arquitectónicas:

  • Reutilización: Un componente hijo puede ser usado en diferentes contextos
  • Separación de responsabilidades: El padre gestiona los datos, el hijo se encarga de la presentación
  • Mantenibilidad: Cambios en la lógica del padre no afectan la implementación del hijo
  • Testabilidad: Componentes independientes son más fáciles de probar

Flujo de datos unidireccional

Angular implementa un flujo de datos unidireccional donde los datos fluyen desde componentes padre hacia componentes hijos. Este patrón, inspirado en arquitecturas como React, hace que el comportamiento de la aplicación sea más predecible y fácil de depurar.

Los datos siempre van "hacia abajo" en la jerarquía, desde el padre hacia los hijos, mientras que los eventos van "hacia arriba", desde los hijos hacia el padre. Esta consistencia direccional evita bucles de dependencias y hace más sencillo rastrear el origen de los cambios en la aplicación.

Preparación para @Input

Para implementar comunicación padre-hijo, Angular proporciona el decorador @Input que permite que un componente hijo reciba datos de su padre. En las siguientes secciones veremos cómo declarar propiedades de entrada y cómo el componente padre puede enviar valores a través de property binding.

También existe una alternativa moderna basada en signals llamada input(), pero la veremos más adelante en el módulo dedicado a Signals. Por ahora nos enfocaremos en el enfoque tradicional con @Input para establecer una base sólida.

Introducción a @Input

El decorador @Input es la herramienta fundamental que Angular proporciona para implementar comunicación padre-hijo. Permite que un componente hijo declare qué propiedades pueden recibir datos desde su componente padre, estableciendo así un canal de comunicación unidireccional.

Sintaxis básica de @Input

Para declarar una propiedad como entrada, simplemente aplicamos el decorador @Input() antes de la declaración de la propiedad en nuestro componente:

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

@Component({
  selector: 'app-product-card',
  template: `
    <div class="product-card">
      <h3>{{ productName }}</h3>
      <p>Precio: {{ price }}€</p>
    </div>
  `
})
export class ProductCardComponent {
  @Input() productName: string = '';
  @Input() price: number = 0;
}

Con esta declaración, nuestro componente ProductCard puede recibir valores para productName y price desde su componente padre.

Enviando datos desde el padre

El componente padre utiliza property binding con corchetes para enviar datos a las propiedades @Input del hijo:

@Component({
  selector: 'app-product-list',
  template: `
    <div class="product-grid">
      <app-product-card 
        [productName]="'iPhone 15'"
        [price]="999">
      </app-product-card>
      
      <app-product-card 
        [productName]="'MacBook Pro'"
        [price]="2499">
      </app-product-card>
    </div>
  `
})
export class ProductListComponent {
  // El padre controla qué datos enviar a cada hijo
}

Datos dinámicos con propiedades del padre

En aplicaciones reales, los datos suelen venir de propiedades dinámicas del componente padre en lugar de valores estáticos:

@Component({
  selector: 'app-product-list',
  template: `
    <div class="product-grid">
      @for (product of products; track product.id) {
        <app-product-card 
          [productName]="product.name"
          [price]="product.price">
        </app-product-card>
      }
    </div>
  `
})
export class ProductListComponent {
  products = [
    { id: 1, name: 'iPhone 15', price: 999 },
    { id: 2, name: 'MacBook Pro', price: 2499 },
    { id: 3, name: 'iPad Air', price: 649 }
  ];
}

Tipos de datos en @Input

Las propiedades @Input pueden recibir cualquier tipo de datos TypeScript:

@Component({
  selector: 'app-user-profile',
  template: `
    <div class="user-profile">
      <h2>{{ user.name }}</h2>
      <p>Activo: {{ isActive ? 'Sí' : 'No' }}</p>
      <ul>
        @for (skill of skills; track skill) {
          <li>{{ skill }}</li>
        }
      </ul>
    </div>
  `
})
export class UserProfileComponent {
  @Input() user!: { name: string; email: string };
  @Input() isActive: boolean = false;
  @Input() skills: string[] = [];
}

Propiedades requeridas y opcionales

Por defecto, todas las propiedades @Input son opcionales. Podemos hacerlas requeridas usando el operador de aserción no nulo (!) o proporcionando valores por defecto:

export class ProductCardComponent {
  // Propiedad requerida (el padre debe enviarla)
  @Input() productName!: string;
  
  // Propiedad opcional con valor por defecto
  @Input() price: number = 0;
  @Input() showDiscount: boolean = false;
}

Alias de propiedades

A veces queremos que el nombre interno de la propiedad sea diferente del nombre que usa el padre. Podemos usar un alias:

export class ProductCardComponent {
  @Input('productTitle') internalName: string = '';
}

El padre lo usaría así:

<app-product-card [productTitle]="product.name"></app-product-card>

Validación básica en el setter

Podemos usar setters para validar o procesar los datos que llegan a través de @Input:

export class ProductCardComponent {
  private _price: number = 0;
  
  @Input() 
  set price(value: number) {
    // Validar que el precio no sea negativo
    this._price = value < 0 ? 0 : value;
  }
  
  get price(): number {
    return this._price;
  }
}

Alternativa moderna: input()

Es importante mencionar que Angular introduce una alternativa moderna a @Input basada en signals llamada input(). Esta nueva API ofrece mejor rendimiento y integración con el sistema de reactividad de signals:

// Alternativa moderna (la veremos en el módulo de Signals)
export class ProductCardComponent {
  productName = input<string>(''); // Signal input
  price = input<number>(0);
}

Sin embargo, en esta lección nos centramos en el enfoque tradicional con @Input porque es fundamental comprender los conceptos base antes de avanzar a las funcionalidades más modernas.

Buenas prácticas con @Input

Al trabajar con @Input, es recomendable seguir estas buenas prácticas:

  • Tipar correctamente: Siempre especifica el tipo de dato esperado
  • Valores por defecto: Proporciona valores sensatos para propiedades opcionales
  • Inmutabilidad: Trata los datos de entrada como inmutables en el componente hijo
  • Validación: Valida los datos de entrada cuando sea necesario
  • Documentación: Comenta el propósito y formato esperado de cada @Input

Con @Input establecemos la base para crear componentes reutilizables y flexibles que pueden adaptarse a diferentes contextos según los datos que reciben de sus padres. En la siguiente sección veremos funcionalidades más avanzadas como las transformaciones automáticas de datos.

@Input avanzado: transforms

Las funciones de transformación en @Input representan una funcionalidad avanzada que permite procesar automáticamente los datos antes de asignarlos a la propiedad del componente. Esta característica elimina la necesidad de usar setters personalizados para transformaciones comunes.

¿Qué son las transforms?

Una transform function es una función pura que recibe el valor de entrada y devuelve el valor transformado. Angular aplica esta función automáticamente cada vez que el componente padre envía un nuevo valor:

import { Component, Input, booleanAttribute, numberAttribute } from '@angular/core';

@Component({
  selector: 'app-config-panel',
  template: `
    <div class="config-panel">
      <p>Modo debug: {{ debugMode ? 'Activado' : 'Desactivado' }}</p>
      <p>Número de elementos: {{ itemCount }}</p>
    </div>
  `
})
export class ConfigPanelComponent {
  @Input({ transform: booleanAttribute }) debugMode: boolean = false;
  @Input({ transform: numberAttribute }) itemCount: number = 0;
}

Transformaciones incorporadas

Angular proporciona funciones de transformación predefinidas para casos comunes:

booleanAttribute - Convierte strings y valores truthy a booleanos:

export class ToggleComponent {
  @Input({ transform: booleanAttribute }) enabled: boolean = false;
}
<!-- Todos estos se convierten a true -->
<app-toggle enabled></app-toggle>
<app-toggle enabled="true"></app-toggle>
<app-toggle enabled=""></app-toggle>
<app-toggle [enabled]="someVariable"></app-toggle>

numberAttribute - Convierte strings a números:

export class CounterComponent {
  @Input({ transform: numberAttribute }) initialValue: number = 0;
}
<!-- Se convierte automáticamente a number -->
<app-counter initialValue="42"></app-counter>
<app-counter [initialValue]="stringNumber"></app-counter>

Funciones de transformación personalizadas

Podemos crear transformaciones personalizadas para casos específicos de nuestra aplicación:

// Función para convertir texto a mayúsculas
function uppercaseTransform(value: string): string {
  return value?.toString().toUpperCase() || '';
}

// Función para formatear precios
function priceTransform(value: number): string {
  return new Intl.NumberFormat('es-ES', {
    style: 'currency',
    currency: 'EUR'
  }).format(value || 0);
}

@Component({
  selector: 'app-product-display',
  template: `
    <div class="product">
      <h3>{{ title }}</h3>
      <p class="price">{{ formattedPrice }}</p>
    </div>
  `
})
export class ProductDisplayComponent {
  @Input({ transform: uppercaseTransform }) title: string = '';
  @Input({ transform: priceTransform }) formattedPrice: string = '';
}

Transformaciones con múltiples parámetros

Las funciones de transformación pueden ser más complejas y manejar diferentes tipos de entrada:

function trimAndCapitalize(value: string | null | undefined): string {
  if (!value) return '';
  
  const trimmed = value.toString().trim();
  return trimmed.charAt(0).toUpperCase() + trimmed.slice(1).toLowerCase();
}

function clampNumber(min: number, max: number) {
  return (value: number): number => {
    const num = Number(value) || 0;
    return Math.min(Math.max(num, min), max);
  };
}

export class UserInputComponent {
  @Input({ transform: trimAndCapitalize }) userName: string = '';
  @Input({ transform: clampNumber(0, 100) }) percentage: number = 0;
}

Transformaciones con arrays y objetos

También podemos transformar estructuras de datos complejas:

function parseTagsFromString(value: string | string[]): string[] {
  if (Array.isArray(value)) return value;
  if (!value) return [];
  
  return value.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0);
}

function normalizeUser(value: any): { name: string; email: string } {
  return {
    name: value?.name || 'Usuario desconocido',
    email: value?.email || 'email@ejemplo.com'
  };
}

export class TaggedContentComponent {
  @Input({ transform: parseTagsFromString }) tags: string[] = [];
  @Input({ transform: normalizeUser }) user!: { name: string; email: string };
}

Combinando transforms con validación

Las transformaciones pueden incluir lógica de validación para garantizar datos consistentes:

function validateAndParseDate(value: string | Date): Date {
  if (value instanceof Date) return value;
  
  const parsed = new Date(value);
  if (isNaN(parsed.getTime())) {
    console.warn(`Fecha inválida recibida: ${value}`);
    return new Date(); // Fecha actual por defecto
  }
  
  return parsed;
}

function sanitizeHtml(value: string): string {
  return value
    ?.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
    ?.replace(/<[^>]*>/g, '') || '';
}

export class ContentComponent {
  @Input({ transform: validateAndParseDate }) publishDate: Date = new Date();
  @Input({ transform: sanitizeHtml }) description: string = '';
}

Ventajas de las transformaciones

El uso de transform functions ofrece múltiples beneficios:

  • Automatización: Las transformaciones se aplican automáticamente sin código adicional
  • Reutilización: Las funciones de transformación se pueden compartir entre componentes
  • Consistencia: Garantizan que los datos siempre lleguen en el formato esperado
  • Simplicidad: Eliminan la necesidad de setters personalizados complejos
  • Performance: Son más eficientes que las transformaciones manuales en templates

Casos de uso prácticos

Las transformaciones son especialmente útiles en estos escenarios comunes:

  • Formularios: Normalizar entrada de usuario (trim, mayúsculas, formato)
  • APIs: Convertir formatos de datos entre frontend y backend
  • Componentes de UI: Estandarizar propiedades de configuración
  • Validación: Asegurar tipos y formatos correctos
  • Localización: Adaptar datos a formatos locales

Consideraciones de rendimiento

Las funciones de transformación deben ser funciones puras sin efectos secundarios para garantizar un comportamiento predecible:

// ✅ Correcto - Función pura
function formatCurrency(value: number): string {
  return new Intl.NumberFormat('es-ES', {
    style: 'currency',
    currency: 'EUR'
  }).format(value || 0);
}

// ❌ Incorrecto - Tiene efectos secundarios
function logAndFormat(value: number): string {
  console.log('Valor recibido:', value); // Efecto secundario
  return formatCurrency(value);
}

Las transform functions representan una herramienta avanzada que mejora significativamente la robustez y mantenibilidad de nuestros componentes, proporcionando un mecanismo elegante para garantizar que los datos siempre lleguen en el formato correcto.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Angular

Documentación oficial de Angular
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Angular

Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender la arquitectura de comunicación padre-hijo en Angular.
  • Aprender a usar el decorador @Input para pasar datos desde un componente padre a un hijo.
  • Conocer cómo enviar datos estáticos y dinámicos mediante property binding.
  • Explorar el uso de transformaciones automáticas en propiedades @Input para validar y formatear datos.
  • Aplicar buenas prácticas en la definición y uso de propiedades @Input para componentes reutilizables.