Angular Signal Inputs

Revolutionize Your Angular Components with the brand new Reactive Signal Inputs.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@kreuzercode

24.01.2024

6 min read

Angular Signal Inputs
share

If you’ve been coding with Angular for a while, you might have had this little wish tucked in the back of your mind — a wish for something more reactive, something that could really jazz up the way we handle component inputs.

Well, guess what? Our coding prayers have been answered! Angular has introduced a game-changer: Signal Inputs. And oh boy, are they exciting!

Why Reactive Inputs?

Before we dive into reactive inputs, let’s first paint a picture of a scenario where they truly shine. Imagine an isEven component. Its task is to take a number and determine if it’s even. Very simple.

As we set out to create our isEven component in Angular, we have two traditional paths: using a setter or ngOnChanges. Let's start by crafting it with a decorator @Input and 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;
  };
}

The isEven component is ready and can be used inside our template in the following way:

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

Fantastic! Having seen the setter method in action, it’s now time to explore the ngOnChanges approach.

@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;
    }
  }
}

Both the setter and ngOnChanges methods are valid approaches in Angular, each effectively reacting to input changes. Yet, at their core, they embody an imperative style.

Now, let’s turn our attention to how Input Signals allow us to come up with a declarative way for this use case.

Input Signals & Computed for the Win 🏅

Input Signals offer a novel API (myInput = input<number>()) where every Input is received 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);
}

Cleaner and purely declarative 🤩.

With Input Signals, Angular is stepping into a future where ngOnChanges lifecycle hooks become a thing of the past. computed Signals and effects are all we need to seamlessly respond to Input changes.

Transform instead of Computed?

While computed properties offer a sleek solution, the same could be achieved using the transform property, let’s take a look.

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
  });
}

Although using the transform function seems like a viable choice, I would advice against it. Transform is limited to a single transformation. What if we want to derive multiple values from the counter Input?

I took this example on X where Alex Rickabaugh from the Angular team gave me a very nice advice which I would summarized like this in my own words:

Keep transforms about tweaking, not twisting. They’re great for a little parsing or coercing, but hey, let’s not change what our Input is really about.

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

Required and Optional Inputs

In our journey so far, we’ve primarily focused on required Inputs. But of course, not all Inputs are mandatory. We can also create optional Inputs with 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

Just as with decorator-based Inputs, Signal Inputs also embrace the concept of aliasing, allowing us to use different names for Inputs. The alias is provided as an options object in the constructor.

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

Route Params as Signal

Modern Angular contains a feature which allows us to access route params as Inputs. To enable this amazing feature we can use the withComponentInputBinding config on the router.

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

With the withComponentInputBinding option enabled on the Router, route parameters are not just accessible as Input properties but now also elegantly transform 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

With Input Signals, we can now seamlessly use their values for executing side effects. Let’s revisit our previous component and log out the Id from the route param with 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());
  }
}

Nice, but logging out an idea is probably not the most sophisticated and realistic thing. How about fetching the TodoItem from a backend via 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));
    });
  }
}

To make this example work, we have to create a dedicated todo Signal.

When we invoke the Signal constructor, it provides us with a WritableSignal. A WritableSignal allows us to change the Signals value via the set or update function.

We then use the subscribe method with a callback function to extract the Todo and set in on the todo Signal.

This approach works but again it has that imperative touch to it. In scenarios like this, it’s probably more advantageous to use the toObservable function to convert id Signal to an Observable.

Once we “Observablify” our Signal we can use switchMap to switch to a stream of our Todo which results from a backend call made inside the TodosService. Last but not least we then again use toSignal to convert our stream back to 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)
      )
    )
  );
}

Great! There’s even a third option for this use case. The ngxtension-platform library brings to the table its computedAsync function, an ideal solution for scenarios like ours.

@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())
  )
}

I personally like the computedAsync function. It’s an elegant solution. Still, if you want to use it across your code base you should also be aware that you lock in to a third party library. As Angular continues to evolve, this library has to be evolved as well. As always, it’s a balance between embracing innovative solutions and not relying on two many third-party libraries.

Wrapping Up

Angular Signals are not just a new feature; they’re a stride towards the era of signal-based components.

These Signals open up a realm of possibilities, allowing us to fully embrace the signal pattern in our Angular projects. This shift heralds a future where reactivity and simplicity become the cornerstones of component design, promising a more intuitive and efficient development experience.

Angular Signals, indeed, are a beacon of innovation, guiding us towards a more dynamic and responsive web development landscape.

Do you enjoy the theme of the code preview? Explore our brand new theme plugin

Skol - the ultimate IDE theme

Skol - the ultimate IDE theme

Northern lights feeling straight to your IDE. A simple but powerful dark theme that looks great and relaxes your eyes.

Prepare yourself for the future of Angular and become an Angular Signals expert today!

Angular Signals Mastercalss eBook

Angular Signals Mastercalss eBook

Discover why Angular Signals are essential, explore their versatile API, and unlock the secrets of their inner workings.

Elevate your development skills and prepare yourself for the future of Angular. Get ahead today!

Get notified
about new blog posts

Sign up for Angular Experts Content Updates & News and you'll get notified whenever I release a new article about Angular, Ngrx, RxJs or other interesting Frontend topics!

We will never share your email with anyone else and you can unsubscribe at any time!

Emails may include additional promotional content, for more details see our Privacy policy.

Responses & comments

Do not hesitate to ask questions and share your own experience and perspective with the topic

Nivek - GDE for Angular & Web Technologies

Nivek

GDE for Angular & Web Technologies

Trainer, Berater und Senior Front-End Engineer mit Schwerpunkt auf dem modernen Web. Er ist sehr erfahren in der Implementierung, Wartung und Verbesserung von Anwendungen und Kernbibliotheken für große Unternehmen.

Kevin ist ständig dabei, sein Wissen zu erweitern und zu teilen. Er unterhält mehrere Open-Source-Projekte, unterrichtet moderne Webtechnologie in Workshops, Podcasts, Videos und Artikeln. Weiter ist er ein beliebter Referent auf Konferenzen. Er schreibt für verschiedene Tech-Publikationen und war 2019 der aktivste Autor der beliebten Angular In-Depth Publikation.

58

Blog posts

2M+

Blog views

39

NPM packages

4M+

Downloaded packages

100+

Videos

15

Celebrated Champions League titles

You might also like

Check out following blog posts from Angular Experts to learn even more about related topics like Angular !

Stärken Sie Ihr Team mit unserer umfassenden Erfahrung

Unsere Angular Experten haben viele Jahre damit verbracht, Unternehmen und Startups zu beraten, Workshops und Tutorials zu leiten und umfangreiche Open-Source-Ressourcen zu pflegen. Wir sind sehr stolz auf unsere Erfahrung im Bereich des modernen Frontends und würden uns freuen auch Ihrem Unternehmen zum Aufschwung zu verhelfen.

or