Alternatives to Angular OnChanges Lifecycle Hook
Whenever you need to know if an @Input() has changed in your angular component, the recommended way is to use the OnChange lifecycle hook. However, from my experience building the YoPrint app, it became apparent that this approach has drawbacks.
Issue 1: OnChange is not type-safe
Type safety is the cornerstone of TypeScript, and yet the SimpleChanges
interface is not type-safe. Therefore, it is necessary to cast the SimpleChange.currentValue
and SimpleChange.previousValue
to the right type before performing additional operations.
In addition to that, you can’t take advantage of IDE / Editor rename variable feature to rename the @Input()
. You must remember to check the OnChange lifecycle hook manually. Hopefully, you have written some test cases to cover these scenarios.
Issue 2: Multiple @Input()
change detection
If you are watching for multiple @Input()
for changes, the OnChange lifecycle will only be called with just the @Input()
changed. Let's say you have @Input()
orderId and @Input()
selectedDate. When either orderId or selectedDate changes, you want to make an API call. Here is a simplified version of the code.
ngOnChanges(changes: SimpleChanges) {
if (changes.orderId || changes.selectedDate) {
const currentOrderId: string = changes.orderId ? changes.orderId.currentValue : this.orderId;
const selectedDate: Date = changes.selectedDate ? changes.selectedDate.currentValue : this.selectedDate;
this.apiService.makeApiCall(currentOrderId, selectedDate);
}
}
Can you simplify it? Yes. It wasn’t immediately apparent reading from the documentation, but it seems like @Input()
values get assigned first before the OnChange lifecycle gets called.
ngOnChanges(changes: SimpleChanges) {
if (changes.orderId || changes.selectedDate) {
this.apiService.makeApiCall(this.orderId, this.selectedDate)
}
}
When should you use NgOnChanges?
- When what changed doesn’t matter. If it doesn’t matter what changed and you are only interested in the fact that something changed, then yes, please use the OnChange lifecycle.
- When you need to everything that changed per cycle. If you need to know if two or more
@Input()
changed together, then the OnChange lifecycle is your friend. For everything else, I recommend using the following approach.
Observable @Input()
with RxJs
private _orderId$ = new BehaviorSubject<string>(null);
@Input() set orderId(value: string) {
this._orderId$.next(value);
}
get orderId() {
return this._orderId$.getValue();
}
private _selectedDate$ = new BehaviorSubject<string>(Date)
@Input() set selectedDate(value: string) {
this._selectedDate$.next(value);
}
get selectedDate() {
return this._selectedDate$.getValue();
}
ngOnInit(): void {
combineLatest([
this._orderId$,
this._selectedDate$,
]).pipe(
switchMap(([orderId, selectedDdate]) => this.apiService.makeApiCall(orderId, selectedDate)),
take(1)
).subscribe(result => {
// Additional Operations
});
}
I used the above approach to simplify the code considerably. In my case, not only I needed to listen for input changes, I needed to also listen for app-wide setting changes too.
A decorator to the rescue?
From the GitHub thread here, there were a few suggestions around using decorators. It’s true it can make the code much simpler but I decided not to use custom decorators for three reasons.
- I need to maintain that decorator. Considering how many places I will be using the decorators, I need to test the decorator more vigorously and make sure it keeps working with every major release of Angular.
- Code assistance from IDE like WebStorm becomes limited. I rely heavily on this feature to boost my productivity.
- The code to add an Observable input is relatively simple. Since it’s just a few lines of code extra per component, I opted for clarity over brevity.
When should you use Observable @Input()
?
I would argue that the best scenario to use it is when you have to make network calls, and you are not using NgRx. If you are not making network calls, you can just use Pipe to capture the same behavior without resorting to Observable @Input()
.
If you are already using NgRx, you should move your network calls to the NgRx Effects module instead.
Whether or not NgRx is right for your project is a topic separate from this article. There are many pros and cons to using NgRx. You should do your due diligence before you commit to NgRx.
I hope this article gave you some insight into design decisions associated with using OnChange lifecycle and it’s alternatives. Let me know if what you think!