@tstdl/base 0.91.4 → 0.91.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { BehaviorSubject, Subject, distinctUntilChanged, filter, firstValueFrom, map, startWith } from 'rxjs';
2
- import { toLazySignal } from '../signals/index.js';
2
+ import { getCurrentSignalsInjector, toLazySignal, untracked } from '../signals/index.js';
3
3
  import { lazyProperty } from '../utils/object/lazy-property.js';
4
4
  export class Collection {
5
5
  sizeSubject = new BehaviorSubject(0);
@@ -50,10 +50,11 @@ export class Collection {
50
50
  return this.size > 0;
51
51
  }
52
52
  constructor() {
53
- lazyProperty(this, '$size', () => toLazySignal(this.size$, { requireSync: true }));
54
- lazyProperty(this, '$observe', () => toLazySignal(this.observe$, { equal: () => false, requireSync: true }));
55
- lazyProperty(this, '$isEmpty', () => toLazySignal(this.isEmpty$, { requireSync: true }));
56
- lazyProperty(this, '$hasItems', () => toLazySignal(this.hasItems$, { requireSync: true }));
53
+ const injector = getCurrentSignalsInjector();
54
+ lazyProperty(this, '$size', () => untracked(() => toLazySignal(this.size$, { injector, requireSync: true })));
55
+ lazyProperty(this, '$observe', () => untracked(() => toLazySignal(this.observe$, { injector, requireSync: true, equal: () => false })));
56
+ lazyProperty(this, '$isEmpty', () => untracked(() => toLazySignal(this.isEmpty$, { injector, requireSync: true })));
57
+ lazyProperty(this, '$hasItems', () => untracked(() => toLazySignal(this.hasItems$, { injector, requireSync: true })));
57
58
  }
58
59
  [Symbol.iterator]() {
59
60
  return this.items();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.91.4",
3
+ "version": "0.91.7",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/signals/api.d.ts CHANGED
@@ -1,6 +1,8 @@
1
+ import type { Tagged } from 'type-fest';
1
2
  import type * as Types from './implementation/index.js';
2
3
  export type { CreateComputedOptions, CreateEffectOptions, CreateSignalOptions, EffectCleanupRegisterFn, EffectRef, Signal, ToSignalOptions, WritableSignal } from './implementation/index.js';
3
- export type SignalsConfiguration = {
4
+ export type Injector = Tagged<symbol, 'injector'>;
5
+ export type SignalsConfiguration<TInjector = Injector> = {
4
6
  signal: typeof Types.signal;
5
7
  computed: typeof Types.computed;
6
8
  effect: typeof Types.effect;
@@ -8,6 +10,8 @@ export type SignalsConfiguration = {
8
10
  isSignal: typeof Types.isSignal;
9
11
  toSignal: typeof Types.toSignal;
10
12
  toObservable: typeof Types.toObservable;
13
+ getCurrentSignalsInjector: () => TInjector;
14
+ runInSignalsInjectionContext: <ReturnT>(injector: TInjector, fn: () => ReturnT) => ReturnT;
11
15
  };
12
16
  export declare let signal: typeof Types.signal;
13
17
  export declare let computed: typeof Types.computed;
@@ -16,4 +20,6 @@ export declare let untracked: typeof Types.untracked;
16
20
  export declare let isSignal: typeof Types.isSignal;
17
21
  export declare let toSignal: typeof Types.toSignal;
18
22
  export declare let toObservable: typeof Types.toObservable;
19
- export declare function configureSignals(configuration: SignalsConfiguration): void;
23
+ export declare let getCurrentSignalsInjector: SignalsConfiguration['getCurrentSignalsInjector'];
24
+ export declare let runInSignalsInjectionContext: SignalsConfiguration['runInSignalsInjectionContext'];
25
+ export declare function configureSignals<TInjector>(configuration: SignalsConfiguration<TInjector>): void;
package/signals/api.js CHANGED
@@ -6,9 +6,11 @@ export let untracked = throwSignalsNotConfigured;
6
6
  export let isSignal = throwSignalsNotConfigured;
7
7
  export let toSignal = throwSignalsNotConfigured;
8
8
  export let toObservable = throwSignalsNotConfigured;
9
+ export let getCurrentSignalsInjector = throwSignalsNotConfigured;
10
+ export let runInSignalsInjectionContext = throwSignalsNotConfigured;
9
11
  /* eslint-enable import/no-mutable-exports */
10
12
  export function configureSignals(configuration) {
11
- ({ signal, computed, effect, untracked, isSignal, toSignal, toObservable } = configuration);
13
+ ({ signal, computed, effect, untracked, isSignal, toSignal, toObservable, runInSignalsInjectionContext, getCurrentSignalsInjector } = configuration);
12
14
  }
13
15
  function throwSignalsNotConfigured() {
14
16
  throw new Error('Signals are not configured. Use configureDefaultSignalsImplementation() for default implementation or configureSignals() for custom implementation.');
@@ -1,2 +1,2 @@
1
- import type { CreateComputedOptions, Signal } from './api.js';
1
+ import { type CreateComputedOptions, type Signal } from './api.js';
2
2
  export declare function computedWithDependencies<T>(computation: () => T, dependencies: Signal<any>[], options?: CreateComputedOptions<T>): Signal<T>;
@@ -1,2 +1,2 @@
1
- import type { CreateEffectOptions, EffectCleanupRegisterFn, EffectRef, Signal } from './api.js';
1
+ import { type CreateEffectOptions, type EffectCleanupRegisterFn, type EffectRef, type Signal } from './api.js';
2
2
  export declare function effectWithDependencies(effectFn: (onCleanup: EffectCleanupRegisterFn) => void, dependencies: Signal<any>[], options?: CreateEffectOptions): EffectRef;
@@ -14,6 +14,8 @@ export function configureDefaultSignalsImplementation() {
14
14
  untracked,
15
15
  isSignal,
16
16
  toSignal,
17
- toObservable
17
+ toObservable,
18
+ getCurrentSignalsInjector: () => null,
19
+ runInSignalsInjectionContext: (_, fn) => fn()
18
20
  });
19
21
  }
@@ -1,3 +1,11 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ import type { Injector } from '../api.js';
1
9
  /**
2
10
  * An effect can, optionally, register a cleanup function. If registered, the cleanup is executed
3
11
  * before the next effect run. The cleanup function makes it possible to "cancel" any work that the
@@ -45,6 +53,20 @@ export interface EffectRef {
45
53
  * Options passed to the `effect` function.
46
54
  */
47
55
  export interface CreateEffectOptions {
56
+ /**
57
+ * The `Injector` in which to create the effect.
58
+ *
59
+ * If this is not provided, the current [injection context](guide/di/dependency-injection-context)
60
+ * will be used instead (via `inject`).
61
+ */
62
+ injector?: Injector;
63
+ /**
64
+ * Whether the `effect` should require manual cleanup.
65
+ *
66
+ * If this is `false` (the default) the effect will automatically register itself to be cleaned up
67
+ * with the current `DestroyRef`.
68
+ */
69
+ manualCleanup?: boolean;
48
70
  /**
49
71
  * Whether the `effect` should allow writing to signals.
50
72
  *
@@ -1,11 +1,4 @@
1
1
  /* eslint-disable */
2
- /**
3
- * @license
4
- * Copyright Google LLC All Rights Reserved.
5
- *
6
- * Use of this source code is governed by an MIT-style license that can be
7
- * found in the LICENSE file at https://angular.io/license
8
- */
9
2
  import { assertNotInReactiveContext } from './asserts.js';
10
3
  import { createWatch } from './watch.js';
11
4
  /**
@@ -1,8 +1,23 @@
1
1
  import { Observable } from 'rxjs';
2
+ import type { Injector } from '../api.js';
2
3
  import type { Signal } from './api.js';
4
+ /**
5
+ * Options for `toObservable`.
6
+ *
7
+ * @developerPreview
8
+ */
9
+ export interface ToObservableOptions {
10
+ /**
11
+ * The `Injector` to use when creating the underlying `effect` which watches the signal.
12
+ *
13
+ * If this isn't specified, the current [injection context](guide/di/dependency-injection-context)
14
+ * will be used.
15
+ */
16
+ injector?: Injector;
17
+ }
3
18
  /**
4
19
  * Exposes the value of an `Signal` as an RxJS `Observable`.
5
20
  *
6
21
  * The signal's value will be propagated into the `Observable`'s subscribers using an `effect`.
7
22
  */
8
- export declare function toObservable<T>(source: Signal<T>): Observable<T>;
23
+ export declare function toObservable<T>(source: Signal<T>, options?: ToObservableOptions): Observable<T>;
@@ -6,7 +6,7 @@ import { untracked } from './untracked.js';
6
6
  *
7
7
  * The signal's value will be propagated into the `Observable`'s subscribers using an `effect`.
8
8
  */
9
- export function toObservable(source) {
9
+ export function toObservable(source, options) {
10
10
  return new Observable((subscriber) => {
11
11
  const effectRef = effect(() => {
12
12
  try {
@@ -16,7 +16,7 @@ export function toObservable(source) {
16
16
  catch (error) {
17
17
  untracked(() => subscriber.error(error));
18
18
  }
19
- }, { allowSignalWrites: true });
19
+ }, { allowSignalWrites: true, injector: options?.injector });
20
20
  return () => effectRef.destroy();
21
21
  });
22
22
  }
@@ -6,6 +6,7 @@
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
8
  import type { Observable, Subscribable } from 'rxjs';
9
+ import { Injector } from '../api.js';
9
10
  import type { Signal } from './api.js';
10
11
  import { ValueEqualityFn } from './equality.js';
11
12
  export interface ToSignalOptions<T> {
@@ -24,6 +25,21 @@ export interface ToSignalOptions<T> {
24
25
  * not met.
25
26
  */
26
27
  requireSync?: boolean;
28
+ /**
29
+ * `Injector` which will provide the `DestroyRef` used to clean up the Observable subscription.
30
+ *
31
+ * If this is not provided, a `DestroyRef` will be retrieved from the current [injection
32
+ * context](guide/di/dependency-injection-context), unless manual cleanup is requested.
33
+ */
34
+ injector?: Injector;
35
+ /**
36
+ * Whether the subscription should be automatically cleaned up (via `DestroyRef`) when
37
+ * `toSignal`'s creation context is destroyed.
38
+ *
39
+ * If manual cleanup is enabled, then `DestroyRef` is not used, and the subscription will persist
40
+ * until the `Observable` itself completes.
41
+ */
42
+ manualCleanup?: boolean;
27
43
  /**
28
44
  * Whether `toSignal` should throw errors from the Observable error channel back to RxJS, where
29
45
  * they'll be processed as uncaught exceptions.
@@ -1,7 +1,8 @@
1
- import { computed, toSignal, untracked } from './api.js';
1
+ import { computed, getCurrentSignalsInjector, runInSignalsInjectionContext, toSignal, untracked } from './api.js';
2
2
  export const toLazySignal = function toLazySignal(source, options) {
3
+ const injector = getCurrentSignalsInjector();
3
4
  let computation = () => {
4
- const signal = untracked(() => toSignal(source, { ...options })); // eslint-disable-line @typescript-eslint/no-unsafe-argument
5
+ const signal = untracked(() => runInSignalsInjectionContext(injector, () => toSignal(source, { ...options }))); // eslint-disable-line @typescript-eslint/no-unsafe-argument
5
6
  const value = signal();
6
7
  computation = signal;
7
8
  return value;