Angular: Transform your inputs at will and simply

nicoss54

Nicolas Frizzarin

Posted on May 24, 2023

Angular: Transform your inputs at will and simply

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" />
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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 />
Enter fullscreen mode Exit fullscreen mode

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,
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
nicoss54
Nicolas Frizzarin

Posted on May 24, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related