• Skip to primary navigation
  • Skip to main content
  • Skip to footer

Codemotion Magazine

We code the future. Together

  • Discover
    • Events
    • Community
    • Partners
    • Become a partner
    • Hackathons
  • Magazine
    • Backend
    • Frontend
    • AI/ML
    • DevOps
    • Dev Life
    • Soft Skills
    • Infographics
  • Talent
    • Discover Talent
    • Jobs
    • Manifesto
  • Companies
  • For Business
    • EN
    • IT
    • ES
  • Sign in
ads

Davide PassafaroFebruary 7, 2024

Angular Signal Inputs: road to Signal Components

Frontend
angular signal components
facebooktwitterlinkedinreddit

Signal Inputs introduction is the initial act of the upcoming rise of Signal Components and zoneless Angular applications, enhancing already both code quality and developer experience. Let’s delve into how they work.

⚠️ ALERT: new Signal Inputs are still in developer preview ⚠️

Recommended article
May 26, 2025

10 React expert tips that will change your life!

Lucilla Tomassi

Lucilla Tomassi

Frontend

Bye @Input( ) decorator; Welcome input( ) function

Creating a Signal Input is quite simple:
rather than creating an input using the @Input( ) decorator, you should now use the input( ) function provided by @angular/core.

Let’s see an example of creating an input of string type:

import { Component, input } from '@angular/core';

@Component({ ... })
  export class MyComponent {
  myProp = input<string>();
}
Code language: TypeScript (typescript)

Using the input( ) function your inputs will be typed as InputSignal, a special type of read-only Signal defined as follows:

* An InputSignal is similar to a non-writable signal except that it also carries additional type-information for transforms, and that Angular internally updates the signal whenever a new value is bound.

More specifically your Signal Inputs will be typed as the following:

myProp: InputSignal<ReadT, WriteT = ReadT> = input<ReadT>(...)
Code language: TypeScript (typescript)

Where ReadT represents the type of the signal value and WriteT represents the type of the expected value from the parent.
Although these types are often the same, I’ll delve deeper into their role and differences discussing the transform function later on.

Let’s go back to the previous example focusing on the input value type:

import { Component, InputSignal, input } from '@angular/core';

@Component({ ... })
  export class MyComponent {
  myProp: InputSignal<string | undefined, string | undefined> = input<string>();
}
Code language: TypeScript (typescript)

Those undefined are given by the optional nature of the input value.

To define your input as required, and thus get rid of those nasty undefined, the input api offers a dedicated required( ) function:

import { Component, InputSignal, input } from '@angular/core';

@Component({ ... })
  export class MyComponent {
  myProp: InputSignal<string, string> = input.required<string>();
}
Code language: TypeScript (typescript)

Alternatively, you can provide a default value to the input( ) function:

import { Component, InputSignal, input } from '@angular/core';

@Component({ ... })
  export class MyComponent {
  myProp: InputSignal<string, string> = input<string>('');
}
Code language: TypeScript (typescript)

The default value can be provided to the required( ) as well.



Read also: Angular Control Flow, the Complete Guide


No more ngOnChanges( )

Nowadays you typically use ngOnChanges and setter functions to perform actions when an input is updated.

With Signal Inputs, you can take advantage of the great flexibility of Signals to get rid of those functions using two powerful tools: computed and effect.

Computed Signals

Using computed you can easily define derived values starting from your inputs, one or more, that will be always updated based on the latest values:

import { Component, InputSignal, Signal, computed, input } from '@angular/core';

@Component({ ... })
export class MyComponent {
  description: InputSignal<string, string> = input<string>('');
  descriptionLength: Signal<number> = computed(() => this.description.length);
}
Code language: TypeScript (typescript)

So each time the description value is modified, the value of descriptionLength is recalculated and updated accordingly.

Effect

With effect, you can define side effects to run when your inputs, one or more, are updated.

For example, imagine you need to update a third-party script you are using to build your chart component when an input is updated:

import Chart from 'third-party-charts';
import { effect, Component, InputSignal, input } from '@angular/core';

@Component({ ... })
export class MyComponent {
  chartData: InputSignal<string[], string[]> = input.required<string[]>();

  constructor() {
    const chart = new Chart({ ... });

    effect((onCleanup) => {
      chart.updateData(this.chartData());

      onCleanup(() => {
        chart.destroy();
      });
    });
  }
}
Code language: TypeScript (typescript)

Or even perform an http request:

import { HttpClient } from '@angular/common/http';
import { effect, Component, InputSignal, inject, input } from '@angular/core';

@Component({ ... })
export class MyComponent {
  myId: InputSignal<string, string> = input.required<string>();

  response: string = '';

  constructor() {
    const httpClient = inject(HttpClient);

    effect((onCleanup) => {
      const sub = httpClient.get<string>(`myurl/${this.myId()}/`)
        .subscribe((resp) => { this.response = resp });

      onCleanup(() => {
        sub.unsubscribe();
      });
    });
  }
}
Code language: TypeScript (typescript)

Using computed and effect will make your components more robust and optimized, enhancing also a lot the maintainability of the code.


Alias and transform function

In order to guarantee a smoother migration from decorator-based inputs, Signal Inputs supports also alias and transform function properties:

import { HttpClient } from '@angular/common/http';
import { effect, Component, InputSignal, inject, input } from '@angular/core';

@Component({ ... })
export class MyComponent {
  textLength: InputSignal<number, string> = input<number, string>(0, {
    alias: 'descriptionText',
    transform: (text) => text.length
  });
}
Code language: TypeScript (typescript)

In particular, thanks to the transform function you can define a function that manipulates your input before it is available in the component scope and this is where the difference between ReadT and WriteT comes into play.

In fact, using the transform function can create a mismatch between the type of the value being set from the parent, represented by WriteT, and the type of the value stored inside your Signal Input, represented by ReadT.

For this reason, when creating a Signal Input with the transform function you can specify both ReadT and WriteT as the function type arguments:

mySimpleProp: InputSignal<ReadT, WriteT = ReadT> = input<ReadT>(...)

myTransformedProp: InputSignal<ReadT, WriteT> = input<ReadT, WriteT>( ... , {
  transform: transformFunction
});
Code language: TypeScript (typescript)

As you can see, without the transform function the value of WriteT is set as identical to ReadT, while using the transform function both ReadT and WriteT are defined distinctly.


What about two-way binding?

Currently, there is no way to implement two-way binding with Signal Inputs, but in the future, there will be an api called Model Input that will expose a set( ) function to fulfill this behavior.

So stay tuned!!!


Thanks for reading so far 🙏

I’d like to have your feedback so please feel free to contact me for any. 👋

Special thanks to Paul Gschwendtner and to the Angular Team for this feature.

Related Posts

Native CSS: A Whole New Story – Part 1

Daniele Carta
March 3, 2025

Understanding Angular — Exploring Dependency Injection and Design Patterns — Part 0 🔥🚀

Giorgio Galassi
February 5, 2025

Let’s Create a Bento Box Design Layout Using Modern CSS

Massimo Avvisati
January 21, 2025
React library: all you need to know about it.

Building reusable multiple-step form in ReactJS

Noa Shtang
August 8, 2024
Share on:facebooktwitterlinkedinreddit
Davide Passafaro
My name is Davide Passafaro and I am Senior Frontend Engineer at Awork. I lead two developer communities in Rome, GDG Roma Città and Angular Rome, and actively contribute to the tech community as a writer and speaker. When i shut down my computer I like to play board games and archery, not both together till now. I also like escape rooms and memes.
5 Best Open Source Databases in 2024
Previous Post
Building Microservices in Python 101
Next Post

Footer

Discover

  • Events
  • Community
  • Partners
  • Become a partner
  • Hackathons

Magazine

  • Tech articles

Talent

  • Discover talent
  • Jobs

Companies

  • Discover companies

For Business

  • Codemotion for companies

About

  • About us
  • Become a contributor
  • Work with us
  • Contact us

Follow Us

© Copyright Codemotion srl Via Marsala, 29/H, 00185 Roma P.IVA 12392791005 | Privacy policy | Terms and conditions