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

Jan 24, 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!

Mastering Angular Signals Ebook

Mastering Angular Signals 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 we release a new blog posts 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.
Kevin Kreuzer - GDE for Angular & Web Technologies

Kevin Kreuzer

Google Developer Expert (GDE)
for Angular & Web Technologies

A trainer, consultant, and senior front-end engineer with a focus on the modern web, as well as a Google Developer Expert for Angular & Web technologies. He is deeply experienced in implementing, maintaining and improving applications and core libraries on behalf of big enterprises worldwide.

Kevin is forever expanding and sharing his knowledge. He maintains and contributes to multiple open-source projects and teaches modern web technologies on stage, in workshops, podcasts, videos and articles. He is a writer for various tech publications and was 2019’s most active Angular In-Depth publication writer.

58

Blog posts

2M

Blog views

23

NPM packages

3M+

Downloaded packages

39

Videos

14

Celebrated Champions League titles

Responses & comments

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

You might also like

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

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

@kreuzercode

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

@kreuzercode

Feb 14, 2023

7 min read

Angular & tRPC

Angular & tRPC

Maximum type safety across the entire stack. How to setup a fullstack app with Angular and tRPC.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@kreuzercode

Jan 24, 2023

6 min read

Empower your team with our extensive experience

Angular Experts have spent many years consulting with enterprises and startups alike, leading workshops and tutorials, and maintaining rich open source resources. We take great pride in our experience in modern front-end and would be thrilled to help your business boom

or