DEV Community

Cover image for Angular: Transform your inputs at will and simply
Nicolas Frizzarin for This is Angular

Posted on

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

Top comments (5)

Collapse
 
bkpecho profile image
Bryan King Pecho

I appreciate the simplicity and readability of the new transform property in the @Input decorator. Angular keeps making development easier!

Collapse
 
ant_f_dev profile image
Anthony Fung

Nice - this will certainly make things a bit easier. Thanks for sharing.

Collapse
 
sampathkumar27896 profile image
KeyboardScript

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.

Collapse
 
nicoss54 profile image
Nicolas Frizzarin

Hey could you provide me an exemple thanks to stackblitz please that I can help you

Best regards

Collapse
 
fullstackreshma profile image
FullStackReshma

That's a great news. It would be very useful for beginner particularly.