Angular Signal Inputs
Revolutionize Your Angular Components with the brand new Reactive Signal Inputs.
Kevin Kreuzer
@kreuzercode
Jan 24, 2024
6 min read
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.
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
. AWritableSignal
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
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
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!
Responses & comments
Do not hesitate to ask questions and share your own experience and perspective with the topic
Nivek
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
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 !
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!
Tomas Trajan
@tomastrajan
Sep 10, 2024
15 min read
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.
Kevin Kreuzer
@kreuzercode
Nov 18, 2023
3 min read
Angular Material components testing
How and why to use Angular Materials component harness to write reliable, stable and readable component tests
Kevin Kreuzer
@kreuzercode
Feb 14, 2023
7 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