angular / angular

Deliver web apps with confidence 🚀
https://angular.dev
MIT License
96.05k stars 25.41k forks source link

new function for signal/rxjs interop #57946

Open ymajoros opened 3 weeks ago

ymajoros commented 3 weeks ago

Which @angular/* package(s) are relevant/related to the feature request?

@angular/core

Description

I propose some function to run some computation that returns an Observable, and convert it to a signal.

Typical workflow:

Proposed solution

export function computedFromObservable<T>(computation: () => Observable<T>, options?: CreateComputedOptions<Observable<T>>): Signal<T | undefined> {
  const computedSignal = computed(computation, options);
  const computedObservable = toObservable(computedSignal);
  const observable = computedObservable.pipe(
    switchAll()
  );
  return toSignal(observable);
}

which can be used this way:

    this.productPage = computedFromObservable(() => this.productService.find$(this.productSearch(), this.pagination()));

... replacing this more complicated construct:

    const productParams: Signal<[ProductSearch, Pagination]> = computed(() => [this.preparationSearch(), this.pagination()]);
    const productPageObservable = toObservable(productParams).pipe(
      switchMap(([productSearch, pagination]) => this.productService.find$(productSearch, pagination))
    );
    this.productPage = toSignal(productPageObservable);

Alternatives considered

Originally, I something like this, but it lacked the power of computed() (single signal as input often requiring to create an additional computed() signal anyway, not as lazy, ...):

export function pipeSignalWithDefault<T, U>(signal: Signal<T>, pipe: OperatorFunction<T, U>, initialValue: U): Signal<U> {
  const signalAsObservable = toObservable(signal);
  const signalPipe = signalAsObservable.pipe(pipe);

  return toSignal(signalPipe, {initialValue});
}
alxhub commented 3 weeks ago

Alternatively, toObservable could be relaxed to take () => T instead of Signal<T> - there's no technical reason why it needs to be a single signal...

ymajoros commented 3 weeks ago

Alternatively, toObservable could be relaxed to take () => T instead of Signal<T> - there's no technical reason why it needs to be a single signal...

Yes, but I'm not sure I understand how it solves the problem. I do like the computed() semantics, but it happens that the typical (e.g. http) internal result is an Observable.

ymajoros commented 3 weeks ago

I added some explanation about usage. Here is an example:

    this.productPage = computedFromObservable(() => this.productService.find$(this.productSearch(), this.pagination()));

... replacing this more complicated construct:

    const productParams: Signal<[ProductSearch, Pagination]> = computed(() => [this.preparationSearch(), this.pagination()]);
    const productPageObservable = toObservable(productParams).pipe(
      switchMap(([productSearch, pagination]) => this.productService.find$(productSearch, pagination))
    );
    this.productPage = toSignal(productPageObservable);
Csellers15 commented 4 days ago

+1 on this