Transitioning to Angular Signal-Based Inputs

Learn how to effectively migrate from traditional @Input decorators to Angular's new signal-based inputs.

Transitioning to Angular Signal-Based Inputs

With the introduction of Angular v17, the Angular team has brought significant improvements to the framework, focusing on reactivity and type safety. One of the key changes is the shift from the traditional @Input decorator to the new signal-based inputs. This article will guide you through the transition from the old approach to the new, showcasing how to use signal-based inputs effectively in your Angular components.

We'll cover various use cases, such as aliasing, transforming inputs, handling required and optional inputs, and more. By the end of this article, you'll be able to easily adopt these new patterns in your projects.

Common Usage Example

Before

@Component({
  selector: 'app-user-profile',
  template: `<p>User: {{ name }}</p>`
})
export class UserProfileComponent {
  @Input() name: string;
}

After

@Component({
  selector: 'app-user-profile',
  template: `<p>User: {{ name() }}</p>`
})
export class UserProfileComponent {
  name = input<string>();
}

In this simple example, the name input is used to pass a value from the parent component to the UserProfileComponent. The old approach uses the @Input decorator, while the new approach uses the input function, exposing name as a signal that can be accessed by calling name() in the template.


Inputs with Default Value

Before

import { Input } from '@angular/core';

@Component({..})
export class UserProfileComponent {
  @Input() age: number = 30;
}

After

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

@Component({..})
export class UserProfileComponent {
  age = input<number>(30);
}

Aliasing an Input

Before

import { Input } from '@angular/core';

@Component({..})
export class UserProfileComponent {
  @Input('userName') name: string = "";
}

After

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

@Component({..})
export class UserProfileComponent {
  name = input<string>("", { alias: 'userName' });
}

Transforming an Input

Before

import { booleanAttribute, Input } from '@angular/core';

@Component({..})
export class ToggleComponent {
  @Input({ transform: booleanAttribute }) disabled: boolean = false;
}

After

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

@Component({..})
export class ToggleComponent {
  disabled = input<boolean>(false, { transform: booleanAttribute });
}

Required Inputs

Before

import { Input } from '@angular/core';

@Component({..})
export class UserProfileComponent {
  @Input({ required: true }) lastName: string;
}

After

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

@Component({..})
export class UserProfileComponent {
  lastName = input.required<string>();
}

Monitoring Input Changes

Before

import { Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({..})
export class UserProfileComponent implements OnChange {
  @Input() firstName: string;

  ngOnChanges(changes: SimpleChanges): void {
    console.log(changes);
  }
}

After

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

@Component({..})
export class UserProfileComponent {
  firstName = input<string>();

  constructor() {
    effect(() => {
      console.log(this.firstName());
    });
  }
}

Conclusion

As we've explored in this article, transitioning to Angular's signal-based inputs brings a new level of reactivity and type safety to your components. This shift aligns with Angular's ongoing efforts to improve performance and developer experience.

By adopting signal-based inputs, you can:

  1. Simplify your component code
  2. Improved change detection
  3. Enhance reactivity in your applications
  4. Reduce the need for lifecycle hooks like ngOnChanges

While the transition might require some initial effort, especially in larger projects, the benefits in terms of code clarity and maintainability are significant.

Keep exploring, keep learning, and happy coding!