Skip to content

Commit

Permalink
fix(derived-async): fix unsubscribing from old observable when new va…
Browse files Browse the repository at this point in the history
…lue is not observable
  • Loading branch information
s.v.zaytsev authored and MillerSvt committed Sep 18, 2024
1 parent 1d7bfd4 commit 71e84e9
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 10 deletions.
38 changes: 36 additions & 2 deletions libs/ngxtension/derived-async/src/derived-async.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Signal, signal } from '@angular/core';
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { Observable, catchError, delay, map, of, startWith } from 'rxjs';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { catchError, delay, map, Observable, of, startWith, timer } from 'rxjs';
import { tap } from 'rxjs/operators';
import { derivedAsync } from './derived-async';

Expand Down Expand Up @@ -383,6 +383,40 @@ describe(derivedAsync.name, () => {
}));
});

describe('works with observables and raw values', () => {
it('waits for them to resolve', fakeAsync(() => {
TestBed.runInInjectionContext(() => {
const logs: number[] = [];
const value = signal(false);

const s = derivedAsync(() => {
return value() ? timer(0, 100) : null;
});

expect(s()).toEqual(undefined); // initial value
TestBed.flushEffects();
expect(s()).toEqual(null); // raw value

value.set(true);
TestBed.flushEffects();
expect(s()).toEqual(null); // still raw value

tick(); // wait for timer to resolve
expect(s()).toEqual(0);

tick(100); // wait for next timer tick
expect(s()).toEqual(1);

value.set(false);
TestBed.flushEffects();
expect(s()).toEqual(null); // raw value

tick(100); // still raw value
expect(s()).toEqual(null);
});
}));
});

describe('works with contextual observables + requireSync', () => {
it('and recovers from errors', fakeAsync(() => {
TestBed.runInInjectionContext(() => {
Expand Down
16 changes: 8 additions & 8 deletions libs/ngxtension/derived-async/src/derived-async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
exhaustAll,
isObservable,
mergeAll,
of,
switchAll,
} from 'rxjs';

Expand Down Expand Up @@ -266,15 +267,14 @@ export function derivedAsync<T>(
return;
}

if (isObservable(newSource) || isPromise(newSource)) {
// we untrack the source$.next() so that we don't register other signals as dependencies
untracked(() => sourceEvent$.next(newSource));
} else {
// if the new source is not an observable or a promise, we set the value immediately
untracked(() =>
sourceValue.set({ kind: StateKind.Value, value: newSource as T }),
// we untrack the source$.next() so that we don't register other signals as dependencies
untracked(() => {
sourceEvent$.next(
isObservable(newSource) || isPromise(newSource)
? newSource
: of(newSource as T),
);
}
});
});

// we return a computed value that will return the current value
Expand Down

0 comments on commit 71e84e9

Please sign in to comment.