Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular signal inputs effect to load data from server

I'm migrating an Angular aplication to use signals and input signals, but found a situation I'm not satisfied with.

With previous @Input() I could define a setter to trigger an "effect" when a specific input changed, and I would setup an Observable for a request to the server, something like

  @Input()
  set id(value: number) {
    this.loading = true;
    this.entity$ = this.service.findById(value).pipe(finalize(() => { 
      this.loading = false;
    }));
  }

I like to avoid using ngOnChanges as I can avoid validating which input changed.

With signals the closest I could find is to use an effect to trigger the query, but the documentation doesn't really recommend it

Effects are rarely needed in most application code, but may be useful in specific circumstances

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.

As I have to set the loading signal to true, I would be breaking the last part

  effect(() => {
    this.loading.set(true);
    this.entity$ = this.service.findById(value).pipe(finalize(() => {
      this.loading.set(false);
    }));
  })

I have also found a way using toObservable() on the input signal, but it uses effetc() under the hood so I suppose I'm limited in the same way.

Is there a recommended approach? Am I dependent of ngOnChanges for this scenarios?

like image 926
Ivo Udelsmann Avatar asked Feb 04 '26 15:02

Ivo Udelsmann


1 Answers

As mentioned in the Signal RFC, writing signals from effects can lead to unexpected behavior and make data flow difficult to follow.This default behavior can be overridden by passing the allowSignalWrites option to the effect creation function.

You can create reusable function for this function accepts an array of signal dependencies and a function that returns an observable

function fromEffect<
  T,
  const Deps extends Signal<any>[],
  Values extends {
    [K in keyof Deps]: Deps[K] extends Signal<infer T> ? T : never;
  }
>(deps: Deps, source: (...values: Values) => Observable<T>, options?: Options) {
  !options?.injector && assertInInjectionContext(fromEffect);
  const injector = options?.injector ?? inject(Injector);

  const sig = signal<T | undefined>(undefined);

  effect(
    (onCleanup) => {
      const values = deps.map((dep) => dep()) as Values;
      const sub = source(...values).subscribe((value) => {
        sig.set(value);
      });

      onCleanup(() => sub.unsubscribe());
    },
    { injector, allowSignalWrites: true }
  );

  return sig.asReadonly();
}

For Detailed explanation you can read the original blog here

this.entity$ = fromEffect([this.id],id => this.service.findById(id).pipe(finalize(() => {
      this.loading.set(false); })))
like image 100
Chellappan வ Avatar answered Feb 06 '26 06:02

Chellappan வ



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!