Introduction
Since the beginning of Angular, parent-child communication is done using @Input() @Output() annotations.
@Input() is a powerful annotation that allows to pass data from a parent component to a child component.
One of the wishes of the community was to be able to transform the data passed in Input in an easy way.
...And soon this wish will become a reality. The purpose of this article is to describe a feature that will arrive very soon and to give you some examples of use.
Context
Let's imagine that we want to transform an input that takes a string as parameter into a boolean.
This transformation would allow us to write
<app-expand expanded />
<app-expand expanded="true" />
<app-expand [expanded]="true" />
To be able to write our calls to our children's components in this way, several ways are available to us:
- the getter and setter method
- create its own annotation
- the transform property of the metatada of the @Input() annotation (not yet released)
Getter and setter
@Component({ selector: 'app-expand' })
export class ExpandComponent {
#expand = false;
@Input() set expanded(value: string | boolean) {
this.#expand = value !== null &&
`${value}` !== 'false';
}
get expanded() {
return this.#expand;
}
}
The getter/setter method is a common pattern in our Angular components.
In the previous piece of code, when the developer sets the input expanded, the setter code is executed and the transformation is performed.
In this transformation I don't check if the passed value is undefined to allow the following HTML writing.
<app-expand expanded />
This solution could be sufficient but has a very big problem.
In general a component has several inputs, if each input requires a transformation, the component may grow for very little added value.
A nicer and more durable solution in the future would be to create your own decorator.
Create your own property decorator
In javascript a decorator is a simple function. In the case of a property decorator, it must be placed before the property and is described by a function taking two parameters:
- target
- key
The key actually represents the name of the property
The target represents the constructor function of the class for a static member, or the prototype of the class for an instance member.
A decorator transforming a string value into a boolean could be the following.
type SafeAny = any;
function toBoolean(value: string | boolean): boolean {
return value !== null && `${value}` !== 'false';
};
function InputBoolean(): (target: SafeAny,name: string) => void {
return function(target: SafeAny, name: string) {
let value = target[name];
Reflect.defineProperty(target, name, {
set(next: string) {
value = toBoolean(next);
},
get() {
return value;
},
enumerable: true,
configurable: true,
})
}
}
This decorator allows us to centralize the transformation logic and can be used as follows in the component:
@Component({ selector: 'app-expand' })
export class ExpandComponent {
@Input() @InputBoolean() expanded = false;
}
Why not using @InputBoolean alone without @Input? AOT compiler needs @Input to be visible.
This method of creating a decorator is elegant and allows to centralize the logic without having unnecessary boilerplate in the components.
Nevertheless, decorators are still an experimental notion and the concept is not necessarily easy to understand at first.
That's why Angular now allows us to use the simple transform property in the metadata of the Input decorator to perform simple transformations.
A new way to transform
Since Angular 16, the @Input decorator takes metadata as a parameter. This recent novelty has been integrated in Angular to make an input required.
Soon these metadata can also take a transformation function.
function toBoolean(value: string | boolean): boolean {
return value !== null && `${value}` !== 'false';
};
@Component({ selector: 'app-expand' })
export class ExpandComponent {
@Input({ transform: toBoolean }) expanded = false;
}
very easy isn't it ? The transformation is done with a simple function which increases the developer experience considerably.
Obviously the Angular team does not stop. Transforming a string into a boolean or a string into a number are quite common. In this sense Angular will provide us with helpers functions to avoid recoding this logic.
import { booleanAttribute, numberAttribute } from '@angular/core';
Top comments (5)
I appreciate the simplicity and readability of the new transform property in the @Input decorator. Angular keeps making development easier!
Nice - this will certainly make things a bit easier. Thanks for sharing.
Hi Nicolas,
Instead of using the 'function' syntax I used arrow syntax to define "toBoolean" method. But angular throws an error that "Input transform must be a function
Value could not be determined statically". I think this is due to the scope of arrow functions and normal functions inside a class but need more explanation and clarity on this.
Hey could you provide me an exemple thanks to stackblitz please that I can help you
Best regards
That's a great news. It would be very useful for beginner particularly.