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.
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
expanded />
expanded="true" />
[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)
@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.
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.
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:
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.
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';