A partir de Angular 16.1
tenemos una nueva opción transform en los @Input
, que nos permite transformar los valores recibidos antes de que estos sean asignados en la propiedad.
@Input({ transform: (value: any) => SomeType) }) aProperty: SomeType
En este artículo hablaremos de su funcionamiento y veremos cual es la problemática principal que viene a solucionar.
Si lo prefieres, el contenido de este artículo también puedes encontrarlo en formato video aquí.
@Input
que no son string
Los inputs son una pieza fundamental ya que nos permiten pasar información desde un componente padre a un hijo.
Pero tienen una limitación y es que si declaramos el input con un tipo distinto a string
estamos obligados a usar el formato del vínculo de propiedades.
Si tomamos como ejemplo un input disabled
del tipo boolean
@Input() disabled: boolean;
Lo ideal sería poder asignar este input utilizando cualquiera de los siguientes formatos:
//❌ Error: '""' no se puede asignar a boolean
<ako-child disabled />
//❌ Error: '"disabled"' no se puede asignar a boolean
<ako-child disabled="disabled" />
<ako-child [disabled]="true" /> // ✅
<ako-child [disabled]="false" /> // ✅
Pero si intentamos asignarlo usando el atributo directamente, como se pasan siempre como string
, nos dará error.
La solución naíf
En principio, la solución parece sencilla. Simplemente añadimos esos dos string
literales al tipo y problema solucionado.
@Input() disabled: '' | 'disabled' | boolean;
Pero esto nos obligaría a que cada vez que esta propiedad fuera usada, tener que implementar una lógica que chequeara los diferentes tipos. Algo realmente tedioso.
Usando un setter
Tanto es así, que la solución de facto hasta ahora cuando se daba este tipo de situaciones era:
- Añadir una propiedad para uso interno marcándola con el tipo deseado.
- Asociar el
@Input
a un setter que aceptara todos esos diferentes tipos de valores, los transformara al tipo deseado y guardara dicho valor en dicha propiedad de uso interno.
_disabled: boolean = false;
@Input() set disabled(value: '' | 'disabled' | boolean | undefined) {
this._disabled = typeof value === 'string' ? true : !!value;
}
Aunque este procedimiento en sí es sencillo, según el número de @Input
va aumentando, nos hace incrementar las líneas de código de nuestra aplicación sin agregar ningún valor añadido.
Las funciones transformadoras (16.1+)
Es principalmente por esto por lo que Angular en la versión 16.1
ha introducido en los @Input
una nueva opción transform
en la cual podremos asignar una función transformadora.
@Input({ transform: (value:any) => any })
Estas funciones tendrán que definir un parámetro por el que recibirán el valor original pasado desde el padre. Y devolver el valor transformado, el cual será finalmente asignado en la propiedad asociada al input.
La función transformadora tendrá que ser a su vez :
- Ligera, para que su ejecución sea rápida.
- Y Pura, para no generar efectos secundarios fuera de la misma.
Funciones ya incluidas en el framework
No poder usar los atributos directamente para asignar boolean
y number
son los casos más comunes en los que esta limitación de los @Input
se hará presente. Por ello el framework ya nos ofrece de entrada las funciones transformadoras para estos dos formatos.
export function booleanAttribute(value: unknown): boolean {
return typeof value === 'boolean' ? value : (value != null && value !== 'false');
}
export function numberAttribute(value: unknown, fallbackValue = NaN): number {
const isNumberValue = !isNaN(parseFloat(value as any)) && !isNaN(Number(value));
return isNumberValue ? Number(value) : fallbackValue;
}
Ambas disponibles en el paquete
@angular/core
Precaución al usarlas
Estas funciones se han creado de una manera genérica para cubrir el mayor número de casos posibles, por lo que es importante tener en cuenta la implementación de las mismas si vamos a usarlas tal cual.
Y es que si nos fijamos, vemos como ambas marcan su parámetro de entrada como del tipo unknown
, lo que permitiría que el padre pasara cualquier tipo de valor por ese input.
//parent.component.html
<ako-child disabled="someRandomValue" /> // ✅
<ako-child [disabled]="{ some: 'Value' }" /> // ✅
<ako-child [disabled]="4405" /> // ✅
//child.component.html
@Component(...)
export class ChildComponent {
@Input({ transform: booleanAttribute}) disabled: boolean = false;
}
Raro será que alguien al usar un @Input
supuestamente booleano intente pasar un número o un objeto, pero si queremos asegurar un uso correcto de nuestros inputs siempre podemos crear nuestras propias funciones transformadoras que sean más restrictivas.
//parent.component.html
<ako-child disabled="disabled" /> // ✅
<ako-child disabled="someRandomValue" /> // ❌
<ako-child [disabled]="true" /> // ✅
<ako-child [disabled]="false" /> // ✅
<ako-child [disabled]="{ some: 'Value' }" /> // ❌
<ako-child [disabled]="4405" /> // ❌
//child.component.html
function disabledAttribute(value: '' | 'disabled' | boolean) {
return booleanAttribute(value);
}
@Component(...)
export class ChildComponent {
@Input({ transform: disabledAttribute}) disabled: boolean = false;
}
Otros usos
Salvar la limitación a la hora de usar atributos para inputs que no sean string
es uno de los principales objetivos de esta nueva funcionalidad. Podemos usarlas para cualquier otro tipo de transformación. Por ejemplo:
Transformar un string
en un objeto Date
//parent.component.html
<ako-child date="2024-04-25" />
//child.component.html
function dateAttribute(value: string): Date {
return new Date(value);
}
@Component(...)
export class ChildComponent {
@Input({ transform: dateAttribute}) date?: Date;
}
Forzar que el input sea siempre un Array
//parent.component.html
<ako-child aProperty="100" />
<ako-child [aProperty]="[100,200,300]" />
//child.component.html
function toArray(value: unknown | unknown[]): unknown[] {
return Array.isArray(value) ? value : [value];
}
@Component(...)
export class ChildComponent {
@Input({ transform: toArray }) aProperty?: unknown[];
}
Y otros muchos más…
Conclusión
En definitiva esta nueva opción transform
nos permite aplicar esas transformaciones a los valores recibidos por los @Input
de una manera mucho más sencilla, limpia y reutilizable.
Si deseas apoyar la creación de más contenido de este tipo, puedes hacerlo a través nuestro Paypal
Top comments (1)
Hi akotech,
Your tips are very useful.
Thanks for sharing.