I keep saying Signal-Based Reactivity is the best in Angular, but it does not mix well with lifecycle hacks. Ionic especially ties screen rendering to lifecycle hooks (ionViewWillEnter, etc.), and ignoring render timing leads to problems like this:
- Rendering a lot of data before a push transition
- The transition animation starts
- Memory leaks and the app crashes
The docs cover this.
However, fetching data during an animation may trigger a large number of DOM operations. This can result in choppy animation.
https://ionicframework.jp/docs/angular/lifecycle/
The simple fix is not to start rendering in ngOnInit or ionViewWillEnter, but to start in ionViewDidEnter. In performance-conscious classes, what runs on each lifecycle hook still matters—and that has not changed.
What about Signal-based development? Right—computed, effect, and httpResource all react when Signals they depend on change. I can also map lifecycle onto Signals by updating them in page lifecycle hooks:
readonly didViewEnter = signal<boolean>(false);
ionViewWillEnter() {
this.didViewEnter.set(false);
}
ionViewDidEnter() {
this.didViewEnter.set(true);
}
But "am I supposed to write this on every page component and maintain it? Seriously?!"—that is how I felt. So let me look for a simpler way to replace Ionic lifecycle with Signals.
First, define a helper like this:
const createDidEnter = <T = any>(el: ElementRef) => {
return new Observable(observer => {
const willEnter = () => observer.next(false);
const didEnter = () => observer.next(true);
el.nativeElement.addEventListener('ionViewWillEnter',willEnter);
el.nativeElement.addEventListener('ionViewDidEnter', didEnter);
return () => {
el.nativeElement.removeEventListener('ionViewWillEnter',willEnter);
el.nativeElement.removeEventListener('ionViewDidEnter', didEnter);
}
}).pipe(startWith(false));
}
Ionic lifecycle hooks also fire as event listeners on the page component (unlike Angular lifecycle). So I build an Observable that emits false on ionViewWillEnter and true on ionViewDidEnter, then convert it to a Signal on the page:
readonly el = inject(ElementRef);
readonly didEnter = toSignal(createDidEnter(this.el));
Now I have a didEnter property driven by Ionic lifecycle. I can write like this:
readonly data = httpResource<data[]>(() => {
const params = new HttpParams().append('search', searchWord());
return !this.didEnter() ? undefined : environment.api + 'manager/movies?' + params.toString();
});
When isReady is false, an empty value is returned; when true, the request runs. Signal-based Ionic development gets even faster from here!