Cómo transformar propiedades de entrada en Angular: decoradores vs signals

⚡ ¿Usas Angular moderno (v17+)? Salta directamente a Transformaciones modernas en Signal inputs con input() para ver la API idiomática con Signals.
Al diseñar componentes reutilizables, muchas veces necesitas adaptar o convertir datos que entran al componente:
- Convertir atributos HTML de tipo string a booleanos (por ejemplo,
<my-button disabled>que pasa""en lugar detrue). - Convertir parámetros de búsqueda o strings en números.
- Mapear modelos de dominio externos (como una lista
Course[]) a modelos de UI de terceros (como unSchedulerEvent[]para un calendario).
Históricamente, hacer esto requería escribir pares de getters y setters con mucho código repetitivo.
En esta guía práctica, te voy a mostrar cómo usar las transformaciones de inputs en Angular utilizando tanto el decorador @Input({ transform }) como la signal moderna input({ transform }).
El problema: getters y setters repetitivos
Antes de las transformaciones de inputs, convertir un booleano o formatear un objeto requería todo este código repetitivo:
// ⚠️ Enfoque legacy: Par de getter/setter tedioso
@Component({ ... })
export class MyButtonComponent {
private _disabled = false;
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean | string) {
this._disabled = typeof value === 'boolean' ? value : value != null && `${value}` !== 'false';
}
}Ahora veamos cómo Angular moderno resuelve esto de forma declarativa.
1. Helpers integrados de conversión (booleanAttribute y numberAttribute)
Angular incluye dos funciones integradas para convertir valores:
import { Component, Input, booleanAttribute, numberAttribute } from '@angular/core';
@Component({
selector: 'app-user-badge',
standalone: true,
template: `
<span [class.active]="isActive">User ID: {{ userId }}</span>
`,
})
export class UserBadgeComponent {
// Convierte <app-user-badge is-active /> o "" en true
@Input({ transform: booleanAttribute }) isActive = false;
// Convierte "42" en 42 (number), con 0 como valor por defecto
@Input({ transform: (val: unknown) => numberAttribute(val, 0) }) userId = 0;
}2. Transformaciones personalizadas para mapear modelos de dominio
Las transformaciones de inputs también son excelentes para adaptar datos externos a los contratos internos de tus componentes, sin ensuciar la interfaz pública.
Por ejemplo, imagina un componente de calendario que recibe una lista Course[] desde tu aplicación, pero necesita pasar SchedulerEvent[] a un calendario de terceros:
export interface Course {
id: string;
title: string;
start: Date;
description: string;
}
export interface SchedulerEvent {
id: string;
title: string;
start: Date;
end: Date;
isAllDay: boolean;
}
function toScheduleEvents(courses: Course[]): SchedulerEvent[] {
return (courses || []).map((course) => ({
id: course.id,
title: course.title,
start: course.start,
end: new Date(course.start.getTime() + 2 * 60 * 60 * 1000),
isAllDay: false,
description: course.description,
}));
}Transformaciones modernas en Signal inputs con input()
En Angular moderno (v17.1+ / v19), usamos Signal inputs con input() o input.required():
// calendar.component.ts (Standalone moderno y Signal Inputs)
import { Component, input, booleanAttribute } from '@angular/core';
import { Course, SchedulerEvent, toScheduleEvents } from './calendar.models';
@Component({
selector: 'app-calendar',
standalone: true,
template: `
<div class="calendar-wrapper">
@if (showGrid()) {
<p>Total Scheduled Events: {{ events().length }}</p>
@for (event of events(); track event.id) {
<div class="event-item">
<strong>{{ event.title }}</strong> - {{ event.start | date:'shortTime' }}
</div>
}
}
</div>
`,
})
export class CalendarComponent {
// 1. Conversión booleana integrada con Signal inputs
showGrid = input(true, { transform: booleanAttribute });
// 2. Transformación con mapeo personalizado: quien consume pasa Course[], el componente lee SchedulerEvent[]
events = input.required<SchedulerEvent[], Course[]>({
transform: toScheduleEvents,
});
}En la plantilla que consume el componente:
<!-- Quien consume el componente pasa Course[] crudo y el atributo string booleano de forma limpia -->
<app-calendar [events]="myCoursesList" showGrid />Comparación: decorador @Input vs Signal input()
| Característica | @Input({ transform }) (v16.1+) | Signal input({ transform }) (v17.1+) |
|---|---|---|
| Reactividad | Propiedad de clase pasiva | Signal reactiva (events()) |
| Inputs requeridos | @Input({ required: true, transform: ... }) | input.required({ transform: ... }) |
| Listo para Zoneless | 🟡 Requiere ciclo de Change Detection | ✅ Actualizaciones precisas y granulares |
| Tipado estricto (Type Narrowing) | (val: string | boolean) => boolean | Tipos explícitos de lectura y escritura con genéricos |
Resumen
Las transformaciones de inputs eliminan el código repetitivo de getters y setters al convertir o mapear datos:
- Usa
booleanAttributepara banderas HTML booleanas (<app-card shadow />). - Usa
numberAttributepara atributos numéricos que llegan como texto. - Usa
input({ transform })con Signal inputs en Angular moderno para lograr transformaciones reactivas y con tipado seguro.
These are my experiences learning and facing my daily challenges working with Angular.
Preguntas Frecuentes
¿Cuál es el propósito de la opción transform en los inputs de Angular?
La opción transform formatea, convierte o mapea valores crudos que entran al input (como atributos string o modelos de datos externos) a los tipos internos del componente, sin tener que escribir pares de getter y setter repetitivos.
¿Qué funciones de transformación integradas incluye Angular?
Angular incluye booleanAttribute (que convierte banderas string como <app-btn disabled /> en true) y numberAttribute (que convierte strings a números de forma segura con valores por defecto).
¿Cómo se usan las transformaciones de inputs con las Signal inputs modernas de Angular?
Pasa una función de transformación en las opciones de la signal input: input(false, { transform: booleanAttribute }) o input.required<User, RawUser>({ transform: toUser }).
¿Puede una función transform cambiar el tipo de TypeScript que aceptan los templates que consumen el componente?
Sí. Las transformaciones de inputs en Angular soportan tipos de escritura distintos (lo que pasa el template consumidor) y tipos de lectura (lo que lee el código del componente internamente).
Artículos Relacionados
Cómo compartir datos entre componentes en Angular (Signals y servicios)
Aprende los mejores patrones para compartir datos entre componentes en Angular. Compara los decoradores clásicos Input/Output y BehaviorSubjects con las Signals modernas: input(), output(), model() y servicios basados en Signals.
Cómo empezar con Standalone Components en Angular (Guía moderna completa)
Aprende a crear aplicaciones en Angular sin módulos usando Standalone Components. Revisa cómo usar bootstrapApplication, app.config.ts, lazy loading con loadComponent y pruebas unitarias con Vitest.
Entendiendo composición vs. herencia en Angular
Por qué la herencia de clases suele generar 'constructor hell' en Angular, y cómo usar la Directive Composition API (hostDirectives), inject() y Signals para escribir código limpio.
Preguntas esenciales de entrevista en Angular para desarrolladores Junior y Mid-Level
Una guía práctica de preguntas de entrevista sobre Angular moderno para roles junior y mid-level, cubriendo Signals, Standalone APIs, Control Flow, Vitest y Zoneless Angular.
Compartir este artículo
Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.
Software real. Lecciones reales.
Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.
Únete a 13,800+ desarrolladores y lectores.
Cero spam. Cancela tu suscripción en cualquier momento.