Input moderno con input()

Intermedio
Angular
Angular
Actualizado: 25/09/2025

API input()

La función input() representa la evolución natural de la comunicación entre componentes en Angular, reemplazando el decorador tradicional @Input() con un enfoque basado en signals. Esta nueva API simplifica la definición de propiedades de entrada y las integra perfectamente con el sistema reactivo de signals.

Sintaxis básica de input()

La función input() se utiliza directamente en la clase del componente para definir propiedades de entrada que son automáticamente signals de solo lectura:

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

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <div class="user-card">
      <h3>{{ name() }}</h3>
      <p>Edad: {{ age() }}</p>
    </div>
  `
})
export class UserCardComponent {
  // Signal input de solo lectura
  name = input<string>('Usuario desconocido');
  age = input<number>(0);
}

Los signal inputs se comportan como signals normales dentro del componente, lo que significa que se accede a sus valores mediante la sintaxis de llamada a función. Angular automáticamente actualiza estos signals cuando el componente padre cambia las propiedades correspondientes.

Comparación con @Input() tradicional

El enfoque tradicional con @Input() requiere más configuración y no proporciona reactividad automática:

Enfoque tradicional con @Input():

@Component({
  selector: 'app-user-card',
  template: `
    <div class="user-card">
      <h3>{{ name }}</h3>
      <p>Edad: {{ age }}</p>
    </div>
  `
})
export class UserCardComponent {
  @Input() name: string = 'Usuario desconocido';
  @Input() age: number = 0;
}

Enfoque moderno con input():

@Component({
  selector: 'app-user-card',
  template: `
    <div class="user-card">
      <h3>{{ name() }}</h3>
      <p>Edad: {{ age() }}</p>
    </div>
  `
})
export class UserCardComponent {
  name = input<string>('Usuario desconocido');
  age = input<number>(0);
}

Ventajas de los signal inputs

Los signal inputs ofrecen múltiples beneficios sobre el sistema tradicional:

  • Reactividad automática: Al ser signals, se integran perfectamente con computed signals y effects, proporcionando actualizaciones automáticas cuando cambian.

  • Type safety mejorado: TypeScript puede inferir mejor los tipos y detectar errores de compilación relacionados con las propiedades de entrada.

  • Simplicidad de sintaxis: No requieren decoradores adicionales ni configuración compleja.

  • Integración con signals: Se pueden combinar fácilmente con otros signals del componente para crear lógica reactiva.

@Component({
  selector: 'app-product-card',
  standalone: true,
  template: `
    <div class="product-card">
      <h3>{{ product().name }}</h3>
      <p class="price">{{ formattedPrice() }}</p>
      <span class="status">{{ stockStatus() }}</span>
    </div>
  `
})
export class ProductCardComponent {
  product = input.required<Product>();
  
  // Computed signal que reacciona automáticamente a los cambios del input
  formattedPrice = computed(() => {
    const price = this.product().price;
    return new Intl.NumberFormat('es-ES', {
      style: 'currency',
      currency: 'EUR'
    }).format(price);
  });
  
  stockStatus = computed(() => {
    return this.product().stock > 0 ? 'En stock' : 'Agotado';
  });
}

Uso en templates del componente padre

Desde el componente padre, los signal inputs se utilizan exactamente igual que los @Input() tradicionales, manteniendo la compatibilidad con la sintaxis existente:

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [UserCardComponent],
  template: `
    <div class="dashboard">
      <app-user-card 
        [name]="userName()" 
        [age]="userAge()">
      </app-user-card>
    </div>
  `
})
export class DashboardComponent {
  userName = signal('Ana García');
  userAge = signal(28);
}

Características importantes de los signal inputs

Los signal inputs son de solo lectura dentro del componente hijo, lo que significa que no se pueden modificar directamente usando métodos como set() o update():

export class UserCardComponent {
  name = input<string>('Usuario desconocido');
  
  updateName() {
    // ❌ Esto no funciona - los signal inputs son read-only
    // this.name.set('Nuevo nombre');
    
    // ✅ Los cambios deben venir del componente padre
    // mediante property binding
  }
}

Esta restricción mantiene el flujo de datos unidireccional y asegura que la fuente de verdad permanezca en el componente padre, siguiendo las mejores prácticas de arquitectura de Angular.

Los signal inputs pueden ser utilizados dentro de effects para reaccionar a cambios en las propiedades de entrada:

export class UserCardComponent {
  name = input<string>('Usuario desconocido');
  age = input<number>(0);
  
  constructor() {
    // Effect que se ejecuta cuando cambian los inputs
    effect(() => {
      console.log(`Usuario actualizado: ${this.name()}, ${this.age()} años`);
    });
  }
}

Esta integración natural con el sistema de effects permite crear lógica reactiva sofisticada que responde automáticamente a los cambios en las propiedades de entrada, eliminando la necesidad de implementar lifecycle hooks como ngOnChanges.

Required inputs y transformaciones

Inputs requeridos con input.required()

Cuando un componente necesita propiedades obligatorias que deben ser proporcionadas por el componente padre, la función input.required() ofrece una solución elegante que mejora la seguridad de tipos y la detección temprana de errores.

@Component({
  selector: 'app-user-profile',
  standalone: true,
  template: `
    <div class="profile">
      <img [src]="avatar()" [alt]="name()">
      <h2>{{ name() }}</h2>
      <p>ID: {{ userId() }}</p>
    </div>
  `
})
export class UserProfileComponent {
  // Input requerido - debe ser proporcionado por el padre
  userId = input.required<string>();
  name = input.required<string>();
  
  // Input opcional con valor por defecto
  avatar = input<string>('/assets/default-avatar.png');
}

Los inputs requeridos proporcionan ventajas significativas en el desarrollo:

  • Validación en tiempo de compilación: TypeScript detecta automáticamente si falta un input requerido en el template del componente padre.

  • Mejor documentación: La API hace explícito qué propiedades son obligatorias para el funcionamiento del componente.

  • Eliminación de valores undefined: Los inputs requeridos garantizan que el valor nunca será undefined, simplificando la lógica del componente.

// Uso desde el componente padre
@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [UserProfileComponent],
  template: `
    <!-- ✅ Correcto - todos los inputs requeridos están presentes -->
    <app-user-profile 
      [userId]="currentUser().id"
      [name]="currentUser().name"
      [avatar]="currentUser().avatar">
    </app-user-profile>
    
    <!-- ❌ Error de TypeScript - falta input requerido 'name' -->
    <app-user-profile [userId]="currentUser().id">
    </app-user-profile>
  `
})
export class DashboardComponent {
  currentUser = signal({
    id: 'user123',
    name: 'Carlos Mendoza',
    avatar: '/assets/carlos.jpg'
  });
}

Transformaciones automáticas de inputs

Las transformaciones de inputs permiten procesar y convertir automáticamente los valores recibidos antes de que estén disponibles en el componente. Esta funcionalidad es especialmente útil para normalizar datos o aplicar conversiones de tipo.

@Component({
  selector: 'app-price-display',
  standalone: true,
  template: `
    <div class="price">
      <span class="amount">{{ price() }}</span>
      <span class="currency">{{ currency() }}</span>
      @if (discountPercent() > 0) {
        <span class="discount">-{{ discountPercent() }}%</span>
      }
    </div>
  `
})
export class PriceDisplayComponent {
  // Transformación para asegurar que el precio sea positivo
  price = input.required<number>({
    transform: (value: number) => Math.max(0, value)
  });
  
  // Transformación para normalizar la moneda
  currency = input<string>('EUR', {
    transform: (value: string) => value.toUpperCase()
  });
  
  // Transformación para convertir string a número
  discountPercent = input<number>(0, {
    transform: (value: string | number) => {
      if (typeof value === 'string') {
        return parseInt(value, 10) || 0;
      }
      return value;
    }
  });
}

Transformaciones comunes y útiles

Las transformaciones más frecuentes incluyen conversiones de tipo, normalización de strings y validaciones básicas:

1 - Conversión de boolean:

export class ToggleComponent {
  isActive = input<boolean>(false, {
    transform: (value: string | boolean) => {
      if (typeof value === 'string') {
        return value === 'true' || value === '';
      }
      return Boolean(value);
    }
  });
}

2 - Normalización de texto:

export class SearchComponent {
  query = input<string>('', {
    transform: (value: string) => value.trim().toLowerCase()
  });
  
  placeholder = input<string>('Buscar...', {
    transform: (value: string) => {
      return value.charAt(0).toUpperCase() + value.slice(1);
    }
  });
}

3 - Validación y limpieza de arrays:

export class TagListComponent {
  tags = input<string[]>([], {
    transform: (value: string[] | string) => {
      if (typeof value === 'string') {
        return value.split(',').map(tag => tag.trim());
      }
      return value.filter(tag => tag && tag.trim().length > 0);
    }
  });
}

Combinando inputs requeridos con transformaciones

Los inputs requeridos pueden incluir transformaciones para garantizar que los datos obligatorios lleguen en el formato correcto:

@Component({
  selector: 'app-date-picker',
  standalone: true,
  template: `
    <div class="date-picker">
      <label>{{ label() }}</label>
      <input 
        type="date" 
        [value]="formattedDate()" 
        [min]="minDate()"
        [max]="maxDate()">
    </div>
  `
})
export class DatePickerComponent {
  // Input requerido con transformación para normalizar fechas
  selectedDate = input.required<Date>({
    transform: (value: string | Date) => {
      if (typeof value === 'string') {
        return new Date(value);
      }
      return value;
    }
  });
  
  label = input.required<string>({
    transform: (value: string) => value.trim()
  });
  
  // Computed signals que utilizan los inputs transformados
  formattedDate = computed(() => {
    return this.selectedDate().toISOString().split('T')[0];
  });
  
  minDate = computed(() => {
    const today = new Date();
    return today.toISOString().split('T')[0];
  });
  
  maxDate = computed(() => {
    const nextYear = new Date();
    nextYear.setFullYear(nextYear.getFullYear() + 1);
    return nextYear.toISOString().split('T')[0];
  });
}

Mejores prácticas para transformaciones

Al implementar transformaciones de inputs, es importante seguir ciertas pautas para mantener la predictibilidad y el rendimiento:

  • Funciones puras: Las transformaciones deben ser funciones puras que no modifiquen el valor original ni produzcan efectos secundarios.

  • Validación de tipos: Siempre validar el tipo de entrada antes de aplicar transformaciones para evitar errores en tiempo de ejecución.

  • Performance: Las transformaciones se ejecutan en cada cambio del input, por lo que deben ser operaciones ligeras y eficientes.

export class OptimizedComponent {
  // ✅ Transformación eficiente y predecible
  normalizedEmail = input<string>('', {
    transform: (value: string) => {
      return typeof value === 'string' ? value.toLowerCase().trim() : '';
    }
  });
  
  // ❌ Evitar transformaciones costosas o con efectos secundarios
  expensiveTransform = input<string>('', {
    transform: (value: string) => {
      // No hacer llamadas HTTP aquí
      // No modificar estado global
      // No realizar operaciones costosas
      return value;
    }
  });
}

Las transformaciones se ejecutan antes de que el valor esté disponible en el signal input, lo que significa que cualquier computed signal o effect que dependa de ese input recibirá automáticamente el valor transformado. Esto garantiza consistencia en todo el componente y elimina la necesidad de aplicar las mismas transformaciones en múltiples lugares.

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 función input() y su uso para definir propiedades de entrada como signals.
  • Diferenciar entre el enfoque tradicional con @Input() y el moderno con input().
  • Aprender a usar inputs requeridos con input.required() para mejorar la seguridad de tipos.
  • Aplicar transformaciones automáticas a los inputs para normalizar y validar datos.
  • Integrar signal inputs con computed signals y effects para crear lógica reactiva en componentes Angular.