Common use cases for effects are well described in official documentation
- Logging data being displayed and when it changes, either for analytics or as a debugging tool
- Keeping data in sync with window.localStorage
- Adding custom DOM behavior that can't be expressed with template syntax
- Performing custom rendering to a , charting library, or other third party UI library
By definition it's a feature that makes you able to do any action when one of the signals that are used inside effect are changed.
You wrote that it doesnt return anything but it is not correct. It returns EffectRef that can be used for cleanups as you do for observables/event listeners/intervals etc.
I'd say the case with calling a service in effect may need a response and may not.
If you don't need a response then there are no issues of just calling it in effect for example to track user actions when you just send request without waiting for response.
If you need the data from server the there might also be different scenarios of how you gonna use it. It can be just for log, for alert message or you really need it to be used on UI.
With first two (alert, log) it's clear that effect if totally fine.
For the case with response data needed for template I'd dig a bit into how signals/effects work to understand if it's dangerous to use it.
The docs says this:
When not to use effects
Avoid using effects for propagation of state changes. This can result in ExpressionChangedAfterItHasBeenChecked errors, infinite circular updates, or unnecessary change detection cycles.
Because of these risks, setting signals is disallowed by default in effects, but can be enabled if absolutely necessary.
They do not recommend doing state changes. But let's cover all these cases
- ExpressionChangedAfterItHasBeenChecked - this can happen due to the nature of effect
Effects always execute asynchronously, during the change detection process. so if you do
constructor() { effect(() => { this.isExceeded.set(this.prop() > 3) }); } onClick(): void { this.prop.update(p => p + 1); }
you should understand that change detection cycle gonna be triggered by zone.js whenever your onClick handler's execution is finished. effect as doc says will run asynchronously so it will be triggered somewhen during or after change detection that will cause this ExpressionChangedAfterItHasBeenChecked exception.
- "infinite circular updates" - I think you have an idea what it means. A small example, not a real world one since makes no sense but still
effect(() => this.prop.set(this.prop() + 1))
- "unnecessary change detection cycle" - this gonna happen when you do e.g. subject.next() and use async pipe in template that will do markForCheck under the hood and start one more CD cycle
In example you wrote I cannot really see any issues of doing something like this:
protected options = signal([]); #sub: Subscription | null = null; constructor() { effect(() => { if(this.#sub) this.#sub.unsubscribe(); // cancel previous ongoing request if it's pending this.#sub = this.myService.fetchData(timeFrame).subscribe(response => { this.options.set(response); }); }, { allowSignalWrites: true }); }
On the first glance when you see allowSignalWrites: true looks scary but if we dig into it and try to understand what can happen wrong with this code - I cannot find any potential issues with it because signal set is executed not directly in effect's root level but in a callback of another asynchronous operation which is service call here. So if you have zone.js enabled then change detection will start executing after your subscribe callback is executed, so signal will be already set before CD starts working. If no zone.js and using signal component approach then signal change will trigger change detection of component's view also on the next tick so no issues.
So 0 smell of any of 3 problem points above are here.