Skip to content

Instantly share code, notes, and snippets.

@eneajaho
Last active September 12, 2023 18:23
Show Gist options
  • Select an option

  • Save eneajaho/33a30bcf217c28b89c95517c07b94266 to your computer and use it in GitHub Desktop.

Select an option

Save eneajaho/33a30bcf217c28b89c95517c07b94266 to your computer and use it in GitHub Desktop.

Revisions

  1. eneajaho revised this gist Aug 18, 2023. 1 changed file with 1 addition and 10 deletions.
    11 changes: 1 addition & 10 deletions computed-from.ts
    Original file line number Diff line number Diff line change
    @@ -1,15 +1,6 @@
    import { isSignal, Signal, untracked } from '@angular/core';
    import { toObservable, toSignal } from '@angular/core/rxjs-interop';
    import {
    combineLatest,
    distinctUntilChanged,
    from,
    isObservable,
    ObservableInput,
    of,
    OperatorFunction,
    take,
    } from 'rxjs';
    import { combineLatest, distinctUntilChanged, from, isObservable, ObservableInput, of, OperatorFunction, take } from 'rxjs';

    export type ObservableSignalInput<T> = ObservableInput<T> | Signal<T>;

  2. eneajaho created this gist Aug 18, 2023.
    79 changes: 79 additions & 0 deletions computed-from.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,79 @@
    import { isSignal, Signal, untracked } from '@angular/core';
    import { toObservable, toSignal } from '@angular/core/rxjs-interop';
    import {
    combineLatest,
    distinctUntilChanged,
    from,
    isObservable,
    ObservableInput,
    of,
    OperatorFunction,
    take,
    } from 'rxjs';

    export type ObservableSignalInput<T> = ObservableInput<T> | Signal<T>;

    /**
    * So that we can have `fn([Observable<A>, Signal<B>]): Observable<[A, B]>`
    */
    type ObservableSignalInputTuple<T> = {
    [K in keyof T]: ObservableSignalInput<T[K]>;
    };

    export function computedFrom<Input extends readonly unknown[], Output = Input>(
    sources: readonly [...ObservableSignalInputTuple<Input>],
    operator?: OperatorFunction<Input, Output>
    ): Signal<Output>;

    export function computedFrom<Input extends object, Output = Input>(
    sources: ObservableSignalInputTuple<Input>,
    operator?: OperatorFunction<Input, Output>
    ): Signal<Output>;

    export function computedFrom(
    sources: any,
    operator?: OperatorFunction<any, any>
    ): Signal<any> {
    let { normalizedSources, initialValues } = Object.entries(sources).reduce(
    (acc, [keyOrIndex, source]) => {
    if (isSignal(source)) {
    acc.normalizedSources[keyOrIndex] = toObservable(source);
    acc.initialValues[keyOrIndex] = untracked(source);
    } else if (isObservable(source)) {
    acc.normalizedSources[keyOrIndex] = source.pipe(distinctUntilChanged());
    source.pipe(take(1)).subscribe((attemptedSyncValue) => {
    if (acc.initialValues[keyOrIndex] !== null) {
    acc.initialValues[keyOrIndex] = attemptedSyncValue;
    }
    });
    acc.initialValues[keyOrIndex] ??= null;
    } else {
    acc.normalizedSources[keyOrIndex] = from(source as any).pipe(
    distinctUntilChanged()
    );
    acc.initialValues[keyOrIndex] = null;
    }

    return acc;
    },
    {
    normalizedSources: Array.isArray(sources) ? [] : {},
    initialValues: Array.isArray(sources) ? [] : {},
    } as {
    normalizedSources: any;
    initialValues: any;
    }
    );

    normalizedSources = combineLatest(normalizedSources);
    if (operator) {
    normalizedSources = normalizedSources.pipe(operator);
    operator(of(initialValues))
    .pipe(take(1))
    .subscribe((newInitialValues) => {
    initialValues = newInitialValues;
    });
    }

    return toSignal(normalizedSources, { initialValue: initialValues });
    }