Don't use @Input()
Use input<T>()
(aka Signal Inputs) instead.
How
export class UserProfileComponent {
@Input()
userId: string;
}
becomes
export class UserProfileComponent {
userId = input<string>();
}
Why
Cleaner and less bug-prone code
In old code, derived properties needed to be updated with lifecycle hooks. Consider the following extension to the above example:
export class UserProfileComponent {
@Input()
userId: string;
user: User;
userDataService = inject(UserDataService);
ngOnInit() {
this.user = this.userDataService.getUser(userId);
}
}
What if userId
changes? Use the OnChanges
lifecycle hook:
export class UserProfileComponent implements OnChanges, OnInit {
@Input()
userId: string;
user: User;
userDataService = inject(UserDataService);
ngOnInit() {
this.user = this.userDataService.getUser(userId);
}
ngOnChanges(changes) {
if (changes.userId) {
this.user = this.userDataService.getUser(userId);
}
}
}
The ngOnInit
block can be omitted (OnChanges
is triggered after OnInit
). There are still two problems:
-
Many developers don’t realize that.
-
It’s imperative which can be harder to understand than declarative style.
-
As components grow large in internal state this approach scales together in complexity.
Using input
signals is better:
export class UserProfileComponent {
userId = input<string>();
userDataService = inject(UserDataService);
user = computed(() => this.userDataService.getUser(this.userId()));
}
This is clean and concise. When userId
changes, user
will be updated automatically. Note: using this component is exactly the same in both cases:
<user-profile userId="currentUserId"></user-profile>
Performance
Signals are faster and will cause less dirty checking in the Angular component tree.