-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
deprecate old Async function, update error-sample
- Loading branch information
1 parent
46ad4b5
commit 04275f1
Showing
4 changed files
with
76 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 20 additions & 33 deletions
53
src/utils/signals/observable-computed.ts → src/utils/signals/old-async-computed.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,78 @@ | ||
/* eslint-disable max-len */ | ||
import { computed, DestroyRef, effect, inject, type Signal, signal } from '@angular/core'; | ||
import { firstValueFrom, isObservable, Observable, type Subscription } from 'rxjs'; | ||
import { isPromise } from './is-promise'; | ||
import { firstValueFrom, isObservable, Observable } from 'rxjs'; | ||
import { isPromise } from 'util/types'; | ||
|
||
type ObservableComputedFn<T> = () => Observable<T> | Promise<T> | T; | ||
type AsyncComputedFn<T> = () => Promise<T> | Observable<T> | T; | ||
interface AsyncComputed { | ||
/** | ||
* @description Helper to get the result of a promise, or the first emission form a observable into an signal. | ||
* @template T type to use for the output signal, defaults to return type of the AsyncComputedFn | ||
* @param {ObservableComputedFn<T>} AsyncComputedFn function returning a promise or observable. the result is put into the signal when it arrives | ||
* @param {AsyncComputedFn<T>} AsyncComputedFn function returning a promise or observable. the result is put into the signal when it arrives | ||
* @returns {*} {(Signal<T | undefined>)} | ||
*/ | ||
<T>(cb: ObservableComputedFn<T>): Signal<T | undefined>; | ||
<T>(cb: AsyncComputedFn<T>): Signal<T | undefined>; | ||
/** | ||
* @description Helper to get the result of a promise, or the first emission form a observable into an signal. | ||
* @template T type to use for the output signal, defaults to return type of the AsyncComputedFn | ||
* @template Y Type to use for the initalValue, default to the type of the initialValue | ||
* @param {ObservableComputedFn<T>} AsyncComputedFn function returning a promise or observable. the result is put into the signal when it arrives | ||
* @param {AsyncComputedFn<T>} AsyncComputedFn function returning a promise or observable. the result is put into the signal when it arrives | ||
* @param {Y} [initialValue] the initial value of the signal. | ||
* @returns {*} {(Signal<T | Y>)} | ||
*/ | ||
<X, Y>(cb: ObservableComputedFn<X>, initialValue: Y): Signal<X | Y>; | ||
<X, Y>(cb: AsyncComputedFn<X>, initialValue: Y): Signal<X | Y>; | ||
/** | ||
* @description Helper to get the result of a promise, or the first emission form a observable into an signal. | ||
* @template T type to use for the output signal, defaults to return type of the AsyncComputedFn | ||
* @template Y Type to use for the initalValue, default to the type of the initialValue | ||
* @param {ObservableComputedFn<T>} AsyncComputedFn function returning a promise or observable. the result is put into the signal when it arrives | ||
* @param {AsyncComputedFn<T>} AsyncComputedFn function returning a promise or observable. the result is put into the signal when it arrives | ||
* @param {Y} [initialValue] the initial value of the signal. | ||
* @param {DestroyRef} [destroyRef] a manual provided destroyRef. Mandatory when the function is used outside a injection context | ||
* @returns {*} {(Signal<T | Y>)} | ||
*/ | ||
<X, Y>(cb: ObservableComputedFn<X>, initialValue: Y, destroyRef: DestroyRef): Signal<X | Y>; | ||
<X, Y>(cb: AsyncComputedFn<X>, initialValue: Y, destroyRef: DestroyRef): Signal<X | Y>; | ||
} | ||
|
||
export const observableComputed: AsyncComputed = <T, Y>( | ||
cb: ObservableComputedFn<T>, | ||
export const oldAsyncComputed: AsyncComputed = <T, Y>( | ||
cb: AsyncComputedFn<T>, | ||
initialValue?: Y, | ||
destroyRef = inject(DestroyRef) | ||
): Signal<T | Y | undefined> => { | ||
const state = signal({ | ||
value: initialValue, | ||
error: undefined, | ||
// not adding the completed state. a Signals has no way to communicate this | ||
// to its consumers without custom wrapping. That is a different concern that | ||
// is outside the scope of this helper | ||
} as { value?: T | Y | undefined; error?: any }); | ||
if (!destroyRef) { | ||
throw new Error('destroyRef is mandatory when used outside a injection context'); | ||
} | ||
destroyRef.onDestroy(() => { | ||
obs?.unsubscribe(); | ||
ref.destroy(); | ||
}); | ||
let obs: Subscription | undefined; | ||
destroyRef.onDestroy(() => ref.destroy()); | ||
const ref = effect( | ||
async () => { | ||
let value: T | Y | undefined; | ||
try { | ||
obs?.unsubscribe(); // cleanup previous subscription (on new signal emission) | ||
const outcome = cb(); | ||
if (isObservable(outcome)) { | ||
obs = outcome.subscribe({ | ||
next: value => state.set({ value, error: undefined }), | ||
error: error => { | ||
state.set({ value: undefined, error }); | ||
}, | ||
}); | ||
value = await firstValueFrom(outcome); | ||
} else if (isPromise(outcome)) { | ||
const value = await outcome; | ||
state.set({ value, error: undefined }); | ||
value = await outcome; | ||
} else { | ||
state.set({ value: outcome, error: undefined }); | ||
value = outcome; | ||
} | ||
state.set({ value, error: undefined }); | ||
} catch (e) { | ||
// this is needed because there might be an error in the CD that is not inside the observable stream | ||
state.set({ value: undefined, error: e }); | ||
} | ||
}, | ||
{ manualCleanup: true, allowSignalWrites: true } | ||
{ manualCleanup: true } | ||
); | ||
|
||
return computed(() => { | ||
const currentState = state(); | ||
if (currentState.error) { | ||
// rethrow error to be handled by the user | ||
throw currentState.error; | ||
// note to self: do not wrap this in a new error, as it will hide the original stack trace | ||
} | ||
return currentState.value; | ||
}); | ||
}; | ||
|
||
|