Transform your Angular components using the cutting-edge Reactive Signal Inputs.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@nivekcode

Jan 24, 2024

6 min read

Angular Signal Inputs
share

If you’ve spent any time building Angular applications, you may have secretly hoped for a more reactive approach to managing component inputs — something that would truly liven up the workflow.

Well, that wish has come true. Angular now offers Signal Inputs, and they’re nothing short of a breakthrough.

Why Reactive Inputs?

To understand the value of reactive inputs, let’s first sketch a use case where they prove indispensable. Suppose we have an isEven component whose job is to accept a number and tell us whether it is even. Straightforward enough.

When building this isEven component in Angular, we face two conventional choices: leveraging a setter or relying on ngOnChanges. Initially, we might implement it with a decorator @Input paired with a setter.

@Component({ 
  standalone: true,
  selector: 'is-even',
  template: `<h1>Is Even: {{ isEven }}</h1>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class IsEvenComponent {
  isEven: boolean | undefined;
  
  @Input({required: true}) set counter(c: number){
    this.isEven = c % 2 === 0;
  };
}

With the isEven component fully wired up, we can now embed it directly within our template like this:

<is-even [counter]="5"/>

The setter method is now behind us—great work. Let’s turn our attention to the ngOnChanges lifecycle hook next.

@Component({
  standalone: true,
  selector: 'is-even',
  template: `<h1>Is Even: {{ isEven }}</h1>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class IsEvenComponent implements OnChanges {
  isEven: boolean | undefined;
  @Input({required: true}) counter!: number;
    
  ngOnChanges(changes: SimpleChanges): void {
    if(changes['counter']){
      this.isEven = changes['counter'].currentValue % 2 === 0;
    }
  }
}

In Angular, both the setter and ngOnChanges represent valid solutions for responding to input changes, but they fundamentally rely on an imperative coding model.

Let’s shift focus now to how Input Signals enable a more declarative approach for handling this scenario.

Input Signals & Computed for the Win 🏅

With Input Signals, a fresh API (myInput = input<number>()) is introduced that delivers each Input as a Signal. 🌟🚀

@Component({
  standalone: true,
  selector: 'is-even',
  template: `<h1>Is Even: {{ isEven() }}</h1>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class IsEvenComponent {
  counter = input.required<number>();
  isEven =  computed(() => this.counter() % 2 === 0);
}

Input Signals mark a shift toward a more elegant, entirely reactive paradigm 😍.

Angular is paving the way for a future where the ngOnChanges lifecycle hook is obsolete. Thanks to Input Signals, computed Signals and effects provide everything necessary to react to Input changes without extra ceremony.

Transform instead of Computed?

Although computed properties present a polished approach, the transform property can deliver an equivalent outcome—let’s examine it.

function isCounterEven(x: number): number {
  return x % 2 === 0;
}
    
@Component({
  standalone: true,
  selector: 'is-even',
  template: `<h1>Is Even: {{ isEven() }}</h1>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class IsEvenComponent {
  isEven = input.required<number, number>({
    alias: 'counter',
    transform: isCounterEven
  });
}

While the transform function appears to be a reasonable solution, I would caution against it. Transform only handles a single operation. Suppose we need to compute several different values from the counter Input?

I came across this discussion on X where Alex Rickabaugh from the Angular team shared a useful tip that I would phrase in my own wording as follows:

Use transforms for adjustments, not for reshaping. They work well for minor parsing or type coercion, but let's avoid altering the fundamental purpose of our Input.

Epic advice from Alex from the Angular team on Angular Input transform

Required and Optional Inputs

Up until now, our discussion has centered mainly on required Inputs. Naturally, though, not every Input has to be mandatory. Optional Inputs can also be built using Signals.

// Optional Input property with undefined as initial value 
counter = input<number>();
    
// Optional Input property with initial value
counter = input(0);
    
// Required Input - does not have a initial value
counter = input.required<number>();

Input Aliasing

Signal Inputs extend the aliasing capability found in decorator-based Inputs, so you can assign distinct external names. This option is supplied through an options object passed into the constructor.

@Component({
  selector: 'user',
  standalone: true,
  template: `{{ customer() }}`,
})
export class UserProfile {
  customer = input<Customer>({ alias: 'user' });
}

Route Params as Signal

In current Angular versions, there's a way to expose route parameters directly as Inputs. This handy capability becomes available by adding the withComponentInputBinding option to the router configuration.

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes, withComponentInputBinding())]
};

When you turn on the withComponentInputBinding option in the Router, route parameters not only become available as standard Input properties but also smoothly convert into Input Signals. 🌟

@Component({
  standalone: true,
  selector: 'todo-item',
  template: ``,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoItemComponent {
  id = input.required<number>();
}

Fetching Data as Side Effect

Input Signals unlock a direct path for running side effects from their values. Let’s take the same component again and output the Id taken from the route param using an effect.

@Component({
  standalone: true,
  selector: 'todo-item',
  template: ``,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoItemComponent {
  id = input.required<number>();
    
  constructor(){
    effect(() => console.log(this.id());
  }
}

That’s a neat trick, though merely logging an idea isn’t exactly a practical or polished use case. What if we instead pull the TodoItem from a remote server using a TodosService?

@Component({
  standalone: true,
  selector: 'todo-item',
  template: `{{ todo() | json }}`,
  imports: [JsonPipe]
})
export default class TodoItemComponent {
  private todoService = inject(TodosService);
  id = input.required<string>();
  todo = signal(null);
    
  constructor() {
    effect(() => {
      this.todoService.getTodo(this.id())
        .subscribe((t) => this.todo.set(t));
    });
  }
}

For this example to function, we need to generate a dedicated todo Signal.

Calling the Signal constructor yields a WritableSignal. Through this WritableSignal, the Signal's value can be modified using the set or update functions.

Afterward, we invoke the subscribe method with a callback function to retrieve the Todo and assign it to the todo Signal.

While this method is functional, it maintains an imperative feel. In such situations, switching to the toObservable function proves more beneficial, as it transforms the id Signal into an Observable.

After converting our Signal to an Observable, we apply switchMap to shift to an Observable stream of the Todo, which is generated from a backend call within the TodosService. Finally, we employ toSignal once more to revert the stream back into a Signal.

@Component({
  standalone: true,
  selector: 'todo-item',
  template: `{{ todo() | json }}`,
  imports: [JsonPipe]
})
export default class TodoItemComponent {
  private todoService = inject(TodosService);
  id = input.required<string>();
  todo = toSignal(
    toObservable(this.id)
      .pipe(
        switchMap((i) => this.todoService.getTodo(i)
      )
    )
  );
}

Excellent — and as it happens, there’s a third alternative tailored to exactly this situation. The ngxtension-platform library offers its own computedAsync utility, which fits our case perfectly.

@Component({
  standalone: true,
  selector: 'todo-item',
  template: `{{ todo() | json }}`,
  imports: [JsonPipe]
})
export default class TodoItemComponent {
  private todoService = inject(TodosService);
  id = input.required<string>();
  todo = computedAsync(
    () => this.todoService.getTodo(this.id())
  )
}

For my own part, I find computedAsync quite appealing — it’s a tidy approach. That said, adopting it across an entire project means committing to a third-party dependency. Given that Angular is under constant development, any external library must keep pace. As is often the case, choosing between cutting-edge solutions and minimizing external reliance is a trade-off.

Wrapping Up

Angular Signals represent more than a mere addition; they mark a significant move towards a signal-driven component model.

With these Signals, a wide range of options emerges, letting us completely incorporate the signal pattern into our Angular applications. This evolution points to a future where reactivity and clarity take center stage in component architecture, offering a more user-friendly and streamlined development workflow.

Indeed, Angular Signals stand as a landmark of innovation, leading us toward a livelier and more adaptable web development ecosystem.

Enjoying how the code samples look? Take a look at our new theme plugin

Skol - the ultimate IDE theme

Skol - the ultimate IDE theme

Bring the aurora borealis experience right into your editor. A minimalist yet powerful dark theme that is both easy on the eyes and aesthetically pleasing.

Leverage AI in your Angular development workflow

Angular + AI Video Course

Angular + AI Video Course

This practical course demonstrates how to bring AI into Angular applications by leveraging Hash Brown for crafting smart, responsive interfaces.

Explore real-time chat, tool invocation, generative UI, structured outputs, and other topics progressively.

Get ready for what’s next in Angular and level up to Angular Signals mastery now!

Angular Signals Masterclass eBook

Angular Signals Mastercalss eBook

Explore why Angular Signals are indispensable, get acquainted with their flexible API, and open up the secrets behind how they work internally.

Take your coding skills to the next level and gear up for what’s coming in Angular. Stay ahead of the curve!

Enjoying the read and ready to become an expert in Angular’s cutting-edge Signal Forms?

Angular Signal Forms: A Practical Deep Dive

Angular Signal Forms: Hands-On Masterclass

Twelve progressive chapters combine theoretical insights with practical exercises to help you master Angular's fresh Signal-Forms.

Explore form fundamentals, validation rules, bespoke controls, nested form structures, transition tactics, and beyond!

Win win deal illustration

Stay in the loop
on fresh articles

Subscribe to the Angular Experts Content Updates & News feed, and we'll let you know the moment new posts about Angular, Ngrx, RxJs, or other exciting Frontend subjects go live!

Your email stays private with us, and unsubscribing is always an option!

While we never share your address, some emails might contain extra promotional material; see our Privacy policy for details.

Your feedback & questions

Feel free to inquire about anything and share your personal insights or viewpoint on the matter

You might also like

Browse these guides from Angular Experts to deepen your knowledge on adjacent subjects such as Angular !

Top 10 Angular Architecture Mistakes You Really Want To Avoid

Top 10 Angular Architecture Mistakes You Really Want To Avoid

In 2024, Angular keeps changing for better with ever increasing pace, but the big picture remains the same which makes architecture know-how timeless and well worth your time!

emoji_objects emoji_objects emoji_objects
Tomas Trajan

Tomas Trajan

@tomastrajan

Sep 10, 2024

15 min read

Improving DX with new Angular @Input Value Transform

Improving DX with new Angular @Input Value Transform

Embrace the Future: Moving Beyond Getters and Setters! Learn how to leverage the power of custom transformers or the build in booleanAttribute and numberAttribute transformers.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@nivekcode

Nov 18, 2023

3 min read

Angular Material components testing

Angular Material components testing

How and why to use Angular Materials component harness to write reliable, stable and readable component tests

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@nivekcode

Feb 14, 2023

7 min read

Put our deep expertise to work for your team

For years, Angular Experts has partnered with both enterprises and early-stage companies, delivering workshops, crafting tutorials, and building a broad set of open source tools. Our hands-on knowledge of the modern front-end landscape is something we are proud of, and we would be delighted to support your growth