Introduction
Parent-child communication in Angular has relied on @Input() and @Output() decorators since the framework's earliest days. The @Input() decorator, in particular, offers a robust mechanism for passing data down from parent components to their children.
A frequently requested community feature has been the ability to easily transform input data during the binding process. That request is finally being addressed — this feature is on the horizon, and this article walks through what's coming along with practical examples.
Context
Consider a scenario where an input accepts a string but needs to be consumed as a boolean within the component. With such a transformation in place, the following syntax becomes possible in templates:
<app-expand expanded />
<app-expand expanded="true" />
<app-expand [expanded]="true" />
Several approaches can achieve this kind of input handling:
- Using a getter and setter pair
- Building a custom property decorator
- Leveraging the transform property in the
@Input()metadata (currently unreleased)
Getter and setter approach
The getter/setter pattern has long been a staple in Angular component design.
@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;
}
}
In that snippet, assigning a value to the expanded input triggers the setter, where the conversion takes place. Notably, the transformation deliberately skips an undefined check to enable the compact template syntax shown below:
<app-expand expanded />
While functional, this method has a significant drawback. Components typically expose multiple inputs; if each requires individual transformation logic, the class bloats quickly with boilerplate that adds little inherent value.
A more elegant, forward-looking alternative is designing a custom decorator.
Building a custom property decorator
In JavaScript, a decorator is fundamentally a function. A property decorator sits above a property declaration and receives two arguments:
targetkey
The key corresponds to the property's name. The target points to either the class constructor (for static members) or the class prototype (for instance members).
A decorator that converts string values into booleans could look like this:
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 helper centralizes the conversion routine and can be applied within a component like so:
@Component({ selector: 'app-expand' })
export class ExpandComponent {
@Input() @InputBoolean() expanded = false;
}
A natural question might be why @InputBoolean isn't used in isolation, without @Input. The reason is that the Ahead-of-Time (AOT) compiler mandates the presence of @Input for proper detection and compilation.
This custom decorator approach is certainly elegant; it isolates transformation logic and eliminates repetitive code within components. Yet, decorators remain an experimental language feature, and the underlying concept can be challenging for developers to grasp initially.
This is precisely why Angular is introducing a straightforward transform property within the Input decorator's metadata to handle common conversion scenarios.
The new transformation API
Starting with Angular 16, the @Input decorator accepts a metadata object. This initial release leveraged metadata to mark inputs as required. The upcoming enhancement extends this metadata to accept 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;
}
That's remarkably straightforward. A simple function handles the conversion, significantly improving the developer experience.
Naturally, the Angular team hasn't stopped there. Conversions like string-to-boolean and string-to-number are ubiquitous patterns. To address this, Angular will ship with built-in helper functions, removing the need to rewrite this conventional logic:
import { booleanAttribute, numberAttribute } from '@angular/core';
