@jscutlery/rx-computed 0.1.1 → 0.2.1
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.
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ npm install @jscutlery/rx-computed
|
|
|
13
13
|
# `rxComputed`
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
|
+
|
|
16
17
|
```typescript
|
|
17
18
|
import { rxComputed } from '@jscutlery/rx-computed';
|
|
18
19
|
|
|
@@ -32,9 +33,25 @@ class MyCmp {
|
|
|
32
33
|
}
|
|
33
34
|
```
|
|
34
35
|
|
|
36
|
+
### Custom Injector
|
|
37
|
+
|
|
38
|
+
{::comment}TODO: Link Injection Context documentation when it is live. https://github.com/angular/angular/pull/49782 {:/comment}
|
|
39
|
+
`rxComputed` utilizes `effect()` underneath so it is **required** to be invoked within an Injection Context. However, a custom `Injector`
|
|
40
|
+
can be passed in and `rxComputed` will invoke within that Injector context.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
class MyCmp {
|
|
44
|
+
injector = inject(Injector);
|
|
45
|
+
|
|
46
|
+
ngOnInit() {
|
|
47
|
+
const value = rxComputed(() => of(16), { injector: this.injector });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
35
52
|
## Motivation
|
|
36
53
|
|
|
37
|
-
Synchronously computed signals in Angular are relatively straight forward.
|
|
54
|
+
Synchronously computed signals in Angular are relatively straight forward. However, when dealing with an asynchronous source of data like an `Observable`, there is no primitive to derive a signal from it.
|
|
38
55
|
|
|
39
56
|
There are two common ways of dealing with this:
|
|
40
57
|
|
|
@@ -79,4 +96,4 @@ effect((onCleanup) => {
|
|
|
79
96
|
|
|
80
97
|
Cf. [Managing RxJS Traffic with Signals and Suspensify](https://marmicode.io/blog/managing-rxjs-traffic-with-signals)
|
|
81
98
|
|
|
82
|
-
Cf. [@jscutlery/operators#suspensify](../operators/README.md#suspensify)
|
|
99
|
+
Cf. [@jscutlery/operators#suspensify](../operators/README.md#suspensify)
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { computed, effect, signal } from '@angular/core';
|
|
1
|
+
import { assertInInjectionContext, computed, effect, inject, Injector, signal, } from '@angular/core';
|
|
2
2
|
import { pending, suspensify } from '@jscutlery/operators';
|
|
3
|
-
export function rxComputed(fn, { initialValue } = {}) {
|
|
3
|
+
export function rxComputed(fn, { initialValue, injector, } = {}) {
|
|
4
|
+
if (!injector)
|
|
5
|
+
assertInInjectionContext(rxComputed);
|
|
6
|
+
injector ??= inject(Injector);
|
|
4
7
|
const sig = signal(pending);
|
|
5
8
|
effect((onCleanup) => {
|
|
6
9
|
const sub = fn()
|
|
7
10
|
.pipe(suspensify())
|
|
8
11
|
.subscribe((value) => sig.set(value));
|
|
9
12
|
onCleanup(() => sub.unsubscribe());
|
|
10
|
-
}, { allowSignalWrites: true });
|
|
13
|
+
}, { allowSignalWrites: true, injector });
|
|
11
14
|
return computed(() => {
|
|
12
15
|
const suspense = sig();
|
|
13
16
|
if (suspense.hasError) {
|
|
@@ -19,4 +22,4 @@ export function rxComputed(fn, { initialValue } = {}) {
|
|
|
19
22
|
return initialValue;
|
|
20
23
|
});
|
|
21
24
|
}
|
|
22
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
25
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicngtY29tcHV0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9yeC1jb21wdXRlZC9zcmMvbGliL3J4LWNvbXB1dGVkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCx3QkFBd0IsRUFDeEIsUUFBUSxFQUNSLE1BQU0sRUFDTixNQUFNLEVBQ04sUUFBUSxFQUNSLE1BQU0sR0FFUCxNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsT0FBTyxFQUFZLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBR3JFLE1BQU0sVUFBVSxVQUFVLENBQ3hCLEVBQXVCLEVBQ3ZCLEVBQ0UsWUFBWSxFQUNaLFFBQVEsTUFDaUQsRUFBRTtJQUU3RCxJQUFJLENBQUMsUUFBUTtRQUFFLHdCQUF3QixDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ3BELFFBQVEsS0FBSyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7SUFFOUIsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUE4QixPQUFPLENBQUMsQ0FBQztJQUV6RCxNQUFNLENBQ0osQ0FBQyxTQUFTLEVBQUUsRUFBRTtRQUNaLE1BQU0sR0FBRyxHQUFHLEVBQUUsRUFBRTthQUNiLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQzthQUNsQixTQUFTLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUN4QyxTQUFTLENBQUMsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7SUFDckMsQ0FBQyxFQUNELEVBQUUsaUJBQWlCLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUN0QyxDQUFDO0lBRUYsT0FBTyxRQUFRLENBQUMsR0FBRyxFQUFFO1FBQ25CLE1BQU0sUUFBUSxHQUFHLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLElBQUksUUFBUSxDQUFDLFFBQVEsRUFBRTtZQUNyQixNQUFNLFFBQVEsQ0FBQyxLQUFLLENBQUM7U0FDdEI7UUFFRCxJQUFJLFFBQVEsQ0FBQyxRQUFRLEVBQUU7WUFDckIsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDO1NBQ3ZCO1FBRUQsT0FBTyxZQUE2QixDQUFDO0lBQ3ZDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIGFzc2VydEluSW5qZWN0aW9uQ29udGV4dCxcbiAgY29tcHV0ZWQsXG4gIGVmZmVjdCxcbiAgaW5qZWN0LFxuICBJbmplY3RvcixcbiAgc2lnbmFsLFxuICBTaWduYWwsXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgcGVuZGluZywgU3VzcGVuc2UsIHN1c3BlbnNpZnkgfSBmcm9tICdAanNjdXRsZXJ5L29wZXJhdG9ycyc7XG5pbXBvcnQgeyBPYnNlcnZhYmxlIH0gZnJvbSAncnhqcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiByeENvbXB1dGVkPFQsIElOSVRJQUxfVkFMVUUgPSBUIHwgbnVsbCB8IHVuZGVmaW5lZD4oXG4gIGZuOiAoKSA9PiBPYnNlcnZhYmxlPFQ+LFxuICB7XG4gICAgaW5pdGlhbFZhbHVlLFxuICAgIGluamVjdG9yLFxuICB9OiB7IGluaXRpYWxWYWx1ZT86IElOSVRJQUxfVkFMVUU7IGluamVjdG9yPzogSW5qZWN0b3IgfSA9IHt9XG4pOiBTaWduYWw8VCB8IElOSVRJQUxfVkFMVUU+IHtcbiAgaWYgKCFpbmplY3RvcikgYXNzZXJ0SW5JbmplY3Rpb25Db250ZXh0KHJ4Q29tcHV0ZWQpO1xuICBpbmplY3RvciA/Pz0gaW5qZWN0KEluamVjdG9yKTtcblxuICBjb25zdCBzaWcgPSBzaWduYWw8U3VzcGVuc2U8VCB8IElOSVRJQUxfVkFMVUU+PihwZW5kaW5nKTtcblxuICBlZmZlY3QoXG4gICAgKG9uQ2xlYW51cCkgPT4ge1xuICAgICAgY29uc3Qgc3ViID0gZm4oKVxuICAgICAgICAucGlwZShzdXNwZW5zaWZ5KCkpXG4gICAgICAgIC5zdWJzY3JpYmUoKHZhbHVlKSA9PiBzaWcuc2V0KHZhbHVlKSk7XG4gICAgICBvbkNsZWFudXAoKCkgPT4gc3ViLnVuc3Vic2NyaWJlKCkpO1xuICAgIH0sXG4gICAgeyBhbGxvd1NpZ25hbFdyaXRlczogdHJ1ZSwgaW5qZWN0b3IgfVxuICApO1xuXG4gIHJldHVybiBjb21wdXRlZCgoKSA9PiB7XG4gICAgY29uc3Qgc3VzcGVuc2UgPSBzaWcoKTtcbiAgICBpZiAoc3VzcGVuc2UuaGFzRXJyb3IpIHtcbiAgICAgIHRocm93IHN1c3BlbnNlLmVycm9yO1xuICAgIH1cblxuICAgIGlmIChzdXNwZW5zZS5oYXNWYWx1ZSkge1xuICAgICAgcmV0dXJuIHN1c3BlbnNlLnZhbHVlO1xuICAgIH1cblxuICAgIHJldHVybiBpbml0aWFsVmFsdWUgYXMgSU5JVElBTF9WQUxVRTtcbiAgfSk7XG59XG4iXX0=
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { signal, effect, computed } from '@angular/core';
|
|
1
|
+
import { assertInInjectionContext, inject, Injector, signal, effect, computed } from '@angular/core';
|
|
2
2
|
import { pending, suspensify } from '@jscutlery/operators';
|
|
3
3
|
|
|
4
|
-
function rxComputed(fn, { initialValue } = {}) {
|
|
4
|
+
function rxComputed(fn, { initialValue, injector, } = {}) {
|
|
5
|
+
if (!injector)
|
|
6
|
+
assertInInjectionContext(rxComputed);
|
|
7
|
+
injector ??= inject(Injector);
|
|
5
8
|
const sig = signal(pending);
|
|
6
9
|
effect((onCleanup) => {
|
|
7
10
|
const sub = fn()
|
|
8
11
|
.pipe(suspensify())
|
|
9
12
|
.subscribe((value) => sig.set(value));
|
|
10
13
|
onCleanup(() => sub.unsubscribe());
|
|
11
|
-
}, { allowSignalWrites: true });
|
|
14
|
+
}, { allowSignalWrites: true, injector });
|
|
12
15
|
return computed(() => {
|
|
13
16
|
const suspense = sig();
|
|
14
17
|
if (suspense.hasError) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jscutlery-rx-computed.mjs","sources":["../../../../packages/rx-computed/src/lib/rx-computed.ts","../../../../packages/rx-computed/src/jscutlery-rx-computed.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"jscutlery-rx-computed.mjs","sources":["../../../../packages/rx-computed/src/lib/rx-computed.ts","../../../../packages/rx-computed/src/jscutlery-rx-computed.ts"],"sourcesContent":["import {\n assertInInjectionContext,\n computed,\n effect,\n inject,\n Injector,\n signal,\n Signal,\n} from '@angular/core';\nimport { pending, Suspense, suspensify } from '@jscutlery/operators';\nimport { Observable } from 'rxjs';\n\nexport function rxComputed<T, INITIAL_VALUE = T | null | undefined>(\n fn: () => Observable<T>,\n {\n initialValue,\n injector,\n }: { initialValue?: INITIAL_VALUE; injector?: Injector } = {}\n): Signal<T | INITIAL_VALUE> {\n if (!injector) assertInInjectionContext(rxComputed);\n injector ??= inject(Injector);\n\n const sig = signal<Suspense<T | INITIAL_VALUE>>(pending);\n\n effect(\n (onCleanup) => {\n const sub = fn()\n .pipe(suspensify())\n .subscribe((value) => sig.set(value));\n onCleanup(() => sub.unsubscribe());\n },\n { allowSignalWrites: true, injector }\n );\n\n return computed(() => {\n const suspense = sig();\n if (suspense.hasError) {\n throw suspense.error;\n }\n\n if (suspense.hasValue) {\n return suspense.value;\n }\n\n return initialValue as INITIAL_VALUE;\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAYM,SAAU,UAAU,CACxB,EAAuB,EACvB,EACE,YAAY,EACZ,QAAQ,GAAA,GACiD,EAAE,EAAA;AAE7D,IAAA,IAAI,CAAC,QAAQ;QAAE,wBAAwB,CAAC,UAAU,CAAC,CAAC;AACpD,IAAA,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,CAAC;AAE9B,IAAA,MAAM,GAAG,GAAG,MAAM,CAA8B,OAAO,CAAC,CAAC;AAEzD,IAAA,MAAM,CACJ,CAAC,SAAS,KAAI;QACZ,MAAM,GAAG,GAAG,EAAE,EAAE;aACb,IAAI,CAAC,UAAU,EAAE,CAAC;AAClB,aAAA,SAAS,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACxC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;KACpC,EACD,EAAE,iBAAiB,EAAE,IAAI,EAAE,QAAQ,EAAE,CACtC,CAAC;IAEF,OAAO,QAAQ,CAAC,MAAK;AACnB,QAAA,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC;QACvB,IAAI,QAAQ,CAAC,QAAQ,EAAE;YACrB,MAAM,QAAQ,CAAC,KAAK,CAAC;AACtB,SAAA;QAED,IAAI,QAAQ,CAAC,QAAQ,EAAE;YACrB,OAAO,QAAQ,CAAC,KAAK,CAAC;AACvB,SAAA;AAED,QAAA,OAAO,YAA6B,CAAC;AACvC,KAAC,CAAC,CAAC;AACL;;AC9CA;;AAEG;;;;"}
|
package/lib/rx-computed.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Signal } from '@angular/core';
|
|
1
|
+
import { Injector, Signal } from '@angular/core';
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
|
-
export declare function rxComputed<T, INITIAL_VALUE = T | null | undefined>(fn: () => Observable<T>, { initialValue }?: {
|
|
3
|
+
export declare function rxComputed<T, INITIAL_VALUE = T | null | undefined>(fn: () => Observable<T>, { initialValue, injector, }?: {
|
|
4
4
|
initialValue?: INITIAL_VALUE;
|
|
5
|
+
injector?: Injector;
|
|
5
6
|
}): Signal<T | INITIAL_VALUE>;
|
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscutlery/rx-computed",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"peerDependencies": {
|
|
5
|
-
"@angular/common": "^16.0.0",
|
|
6
5
|
"@angular/core": "^16.0.0",
|
|
7
|
-
"
|
|
8
|
-
"@jscutlery/operators": "2.1.2"
|
|
6
|
+
"@jscutlery/operators": "^2.0.0"
|
|
9
7
|
},
|
|
10
8
|
"dependencies": {
|
|
11
9
|
"tslib": "^2.4.0"
|
|
@@ -24,4 +22,4 @@
|
|
|
24
22
|
"default": "./fesm2022/jscutlery-rx-computed.mjs"
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
|
-
}
|
|
25
|
+
}
|