@jscutlery/rx-computed 0.2.5 → 0.2.6

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.
@@ -0,0 +1,2 @@
1
+ export { rxComputed } from './lib/rx-computed';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wYWNrYWdlcy9yeC1jb21wdXRlZC9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG1CQUFtQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHsgcnhDb21wdXRlZCB9IGZyb20gJy4vbGliL3J4LWNvbXB1dGVkJzsiXX0=
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ export * from './index';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoianNjdXRsZXJ5LXJ4LWNvbXB1dGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcGFja2FnZXMvcngtY29tcHV0ZWQvc3JjL2pzY3V0bGVyeS1yeC1jb21wdXRlZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsU0FBUyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHZW5lcmF0ZWQgYnVuZGxlIGluZGV4LiBEbyBub3QgZWRpdC5cbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL2luZGV4JztcbiJdfQ==
@@ -0,0 +1,25 @@
1
+ import { assertInInjectionContext, computed, effect, inject, Injector, signal, } from '@angular/core';
2
+ import { pending, suspensify } from '@jscutlery/operators';
3
+ export function rxComputed(fn, { initialValue, injector, } = {}) {
4
+ if (!injector)
5
+ assertInInjectionContext(rxComputed);
6
+ injector ??= inject(Injector);
7
+ const sig = signal(pending);
8
+ effect((onCleanup) => {
9
+ const sub = fn()
10
+ .pipe(suspensify())
11
+ .subscribe((value) => sig.set(value));
12
+ onCleanup(() => sub.unsubscribe());
13
+ }, { allowSignalWrites: true, injector });
14
+ return computed(() => {
15
+ const suspense = sig();
16
+ if (suspense.hasError) {
17
+ throw suspense.error;
18
+ }
19
+ if (suspense.hasValue) {
20
+ return suspense.value;
21
+ }
22
+ return initialValue;
23
+ });
24
+ }
25
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicngtY29tcHV0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9yeC1jb21wdXRlZC9zcmMvbGliL3J4LWNvbXB1dGVkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCx3QkFBd0IsRUFDeEIsUUFBUSxFQUNSLE1BQU0sRUFDTixNQUFNLEVBQ04sUUFBUSxFQUNSLE1BQU0sR0FFUCxNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsT0FBTyxFQUFZLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBR3JFLE1BQU0sVUFBVSxVQUFVLENBQ3hCLEVBQXVCLEVBQ3ZCLEVBQ0UsWUFBWSxFQUNaLFFBQVEsTUFDaUQsRUFBRTtJQUU3RCxJQUFJLENBQUMsUUFBUTtRQUFFLHdCQUF3QixDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ3BELFFBQVEsS0FBSyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7SUFFOUIsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUE4QixPQUFPLENBQUMsQ0FBQztJQUV6RCxNQUFNLENBQ0osQ0FBQyxTQUFTLEVBQUUsRUFBRTtRQUNaLE1BQU0sR0FBRyxHQUFHLEVBQUUsRUFBRTthQUNiLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQzthQUNsQixTQUFTLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUN4QyxTQUFTLENBQUMsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7SUFDckMsQ0FBQyxFQUNELEVBQUUsaUJBQWlCLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUN0QyxDQUFDO0lBRUYsT0FBTyxRQUFRLENBQUMsR0FBRyxFQUFFO1FBQ25CLE1BQU0sUUFBUSxHQUFHLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLElBQUksUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3RCLE1BQU0sUUFBUSxDQUFDLEtBQUssQ0FBQztRQUN2QixDQUFDO1FBRUQsSUFBSSxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDdEIsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDO1FBQ3hCLENBQUM7UUFFRCxPQUFPLFlBQTZCLENBQUM7SUFDdkMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgYXNzZXJ0SW5JbmplY3Rpb25Db250ZXh0LFxuICBjb21wdXRlZCxcbiAgZWZmZWN0LFxuICBpbmplY3QsXG4gIEluamVjdG9yLFxuICBzaWduYWwsXG4gIFNpZ25hbCxcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBwZW5kaW5nLCBTdXNwZW5zZSwgc3VzcGVuc2lmeSB9IGZyb20gJ0Bqc2N1dGxlcnkvb3BlcmF0b3JzJztcbmltcG9ydCB7IE9ic2VydmFibGUgfSBmcm9tICdyeGpzJztcblxuZXhwb3J0IGZ1bmN0aW9uIHJ4Q29tcHV0ZWQ8VCwgSU5JVElBTF9WQUxVRSA9IFQgfCBudWxsIHwgdW5kZWZpbmVkPihcbiAgZm46ICgpID0+IE9ic2VydmFibGU8VD4sXG4gIHtcbiAgICBpbml0aWFsVmFsdWUsXG4gICAgaW5qZWN0b3IsXG4gIH06IHsgaW5pdGlhbFZhbHVlPzogSU5JVElBTF9WQUxVRTsgaW5qZWN0b3I/OiBJbmplY3RvciB9ID0ge31cbik6IFNpZ25hbDxUIHwgSU5JVElBTF9WQUxVRT4ge1xuICBpZiAoIWluamVjdG9yKSBhc3NlcnRJbkluamVjdGlvbkNvbnRleHQocnhDb21wdXRlZCk7XG4gIGluamVjdG9yID8/PSBpbmplY3QoSW5qZWN0b3IpO1xuXG4gIGNvbnN0IHNpZyA9IHNpZ25hbDxTdXNwZW5zZTxUIHwgSU5JVElBTF9WQUxVRT4+KHBlbmRpbmcpO1xuXG4gIGVmZmVjdChcbiAgICAob25DbGVhbnVwKSA9PiB7XG4gICAgICBjb25zdCBzdWIgPSBmbigpXG4gICAgICAgIC5waXBlKHN1c3BlbnNpZnkoKSlcbiAgICAgICAgLnN1YnNjcmliZSgodmFsdWUpID0+IHNpZy5zZXQodmFsdWUpKTtcbiAgICAgIG9uQ2xlYW51cCgoKSA9PiBzdWIudW5zdWJzY3JpYmUoKSk7XG4gICAgfSxcbiAgICB7IGFsbG93U2lnbmFsV3JpdGVzOiB0cnVlLCBpbmplY3RvciB9XG4gICk7XG5cbiAgcmV0dXJuIGNvbXB1dGVkKCgpID0+IHtcbiAgICBjb25zdCBzdXNwZW5zZSA9IHNpZygpO1xuICAgIGlmIChzdXNwZW5zZS5oYXNFcnJvcikge1xuICAgICAgdGhyb3cgc3VzcGVuc2UuZXJyb3I7XG4gICAgfVxuXG4gICAgaWYgKHN1c3BlbnNlLmhhc1ZhbHVlKSB7XG4gICAgICByZXR1cm4gc3VzcGVuc2UudmFsdWU7XG4gICAgfVxuXG4gICAgcmV0dXJuIGluaXRpYWxWYWx1ZSBhcyBJTklUSUFMX1ZBTFVFO1xuICB9KTtcbn1cbiJdfQ==
@@ -0,0 +1,32 @@
1
+ import { assertInInjectionContext, inject, Injector, signal, effect, computed } from '@angular/core';
2
+ import { pending, suspensify } from '@jscutlery/operators';
3
+
4
+ function rxComputed(fn, { initialValue, injector, } = {}) {
5
+ if (!injector)
6
+ assertInInjectionContext(rxComputed);
7
+ injector ??= inject(Injector);
8
+ const sig = signal(pending);
9
+ effect((onCleanup) => {
10
+ const sub = fn()
11
+ .pipe(suspensify())
12
+ .subscribe((value) => sig.set(value));
13
+ onCleanup(() => sub.unsubscribe());
14
+ }, { allowSignalWrites: true, injector });
15
+ return computed(() => {
16
+ const suspense = sig();
17
+ if (suspense.hasError) {
18
+ throw suspense.error;
19
+ }
20
+ if (suspense.hasValue) {
21
+ return suspense.value;
22
+ }
23
+ return initialValue;
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Generated bundle index. Do not edit.
29
+ */
30
+
31
+ export { rxComputed };
32
+ //# sourceMappingURL=jscutlery-rx-computed.mjs.map
@@ -0,0 +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 {\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;AACvB,QAAA,IAAI,QAAQ,CAAC,QAAQ,EAAE;YACrB,MAAM,QAAQ,CAAC,KAAK,CAAC;SACtB;AAED,QAAA,IAAI,QAAQ,CAAC,QAAQ,EAAE;YACrB,OAAO,QAAQ,CAAC,KAAK,CAAC;SACvB;AAED,QAAA,OAAO,YAA6B,CAAC;AACvC,KAAC,CAAC,CAAC;AACL;;AC9CA;;AAEG;;;;"}
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { rxComputed } from './lib/rx-computed';
@@ -0,0 +1,6 @@
1
+ import { Injector, Signal } from '@angular/core';
2
+ import { Observable } from 'rxjs';
3
+ export declare function rxComputed<T, INITIAL_VALUE = T | null | undefined>(fn: () => Observable<T>, { initialValue, injector, }?: {
4
+ initialValue?: INITIAL_VALUE;
5
+ injector?: Injector;
6
+ }): Signal<T | INITIAL_VALUE>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscutlery/rx-computed",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "peerDependencies": {
5
5
  "@angular/core": "^16.0.0",
6
6
  "@jscutlery/operators": "^2.0.0"
@@ -8,5 +8,18 @@
8
8
  "dependencies": {
9
9
  "tslib": "^2.4.0"
10
10
  },
11
- "sideEffects": false
12
- }
11
+ "sideEffects": false,
12
+ "module": "fesm2022/jscutlery-rx-computed.mjs",
13
+ "typings": "index.d.ts",
14
+ "exports": {
15
+ "./package.json": {
16
+ "default": "./package.json"
17
+ },
18
+ ".": {
19
+ "types": "./index.d.ts",
20
+ "esm2022": "./esm2022/jscutlery-rx-computed.mjs",
21
+ "esm": "./esm2022/jscutlery-rx-computed.mjs",
22
+ "default": "./fesm2022/jscutlery-rx-computed.mjs"
23
+ }
24
+ }
25
+ }
package/.eslintrc.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "extends": ["../../.eslintrc.json"],
3
- "ignorePatterns": ["!**/*"],
4
- "overrides": [
5
- {
6
- "files": ["*.ts"],
7
- "rules": {
8
- "@angular-eslint/directive-selector": [
9
- "error",
10
- {
11
- "type": "attribute",
12
- "prefix": "jscutlery",
13
- "style": "camelCase"
14
- }
15
- ],
16
- "@angular-eslint/component-selector": [
17
- "error",
18
- {
19
- "type": "element",
20
- "prefix": "jscutlery",
21
- "style": "kebab-case"
22
- }
23
- ]
24
- },
25
- "extends": [
26
- "plugin:@nx/angular",
27
- "plugin:@angular-eslint/template/process-inline-templates"
28
- ]
29
- },
30
- {
31
- "files": ["*.html"],
32
- "extends": ["plugin:@nx/angular-template"],
33
- "rules": {}
34
- }
35
- ]
36
- }
package/CHANGELOG.md DELETED
@@ -1,43 +0,0 @@
1
- ## 0.2.5 (2024-03-24)
2
-
3
-
4
- ### 🩹 Fixes
5
-
6
- - import of zone.js and zone.js/testing ([f14b091](https://github.com/jscutlery/devkit/commit/f14b091))
7
-
8
-
9
- ### ❤️ Thank You
10
-
11
- - Adrian Baran
12
- - Younes Jaaidi
13
-
14
- ## [0.2.4](https://github.com/jscutlery/devkit/compare/rx-computed-0.2.3...rx-computed-0.2.4) (2023-10-25)
15
-
16
- ## [0.2.3](https://github.com/jscutlery/devkit/compare/rx-computed-0.2.2...rx-computed-0.2.3) (2023-07-17)
17
-
18
- ## [0.2.2](https://github.com/jscutlery/devkit/compare/rx-computed-0.2.1...rx-computed-0.2.2) (2023-06-02)
19
-
20
- ## [0.2.1](https://github.com/jscutlery/devkit/compare/rx-computed-0.2.0...rx-computed-0.2.1) (2023-05-25)
21
-
22
- ### Bug Fixes
23
-
24
- - **rx-computed:** adjust peer dependencies ([30277ca](https://github.com/jscutlery/devkit/commit/30277ca6e14d68c19497fa280e95ed6a262d562b))
25
-
26
- # [0.2.0](https://github.com/jscutlery/devkit/compare/rx-computed-0.1.1...rx-computed-0.2.0) (2023-05-25)
27
-
28
- ### Features
29
-
30
- - **rx-computed:** allow rxComputed to accept a custom Injector ([6f3228a](https://github.com/jscutlery/devkit/commit/6f3228a0413070c3f4fd0178420b204a2e825f04))
31
-
32
- ## [0.1.1](https://github.com/jscutlery/devkit/compare/rx-computed-0.1.0...rx-computed-0.1.1) (2023-05-25)
33
-
34
- # 0.1.0 (2023-05-25)
35
-
36
- ### Features
37
-
38
- - **rx-computed:** ✨ add rxComputed ([994ed28](https://github.com/jscutlery/devkit/commit/994ed283c5ee4068eac424ceb5c1b4d3f09b891d))
39
- - **rx-computed:** ✨ handle async source ([3870ebb](https://github.com/jscutlery/devkit/commit/3870ebbbabd6fba0f2491c9cbe45d1feba723b47))
40
- - **rx-computed:** ✨ handle custom initial value ([87f8273](https://github.com/jscutlery/devkit/commit/87f827394461431ebb7e2766b7dae6a418c98a46))
41
- - **rx-computed:** ✨ throw error if source emits error ([e75ee40](https://github.com/jscutlery/devkit/commit/e75ee408720a88e1be0582cd89eb782fbf92efe7))
42
- - **rx-computed:** ✨ unsubscribe when depdency changes ([ddcb24a](https://github.com/jscutlery/devkit/commit/ddcb24ac4f8438008638661963c81e8b46ad8424))
43
- - **rx-computed:** ✨return undefined as default initial value ([b9b525d](https://github.com/jscutlery/devkit/commit/b9b525d08b50eb0bca6f5d5386f4982eba152f06))
package/jest.config.ts DELETED
@@ -1,22 +0,0 @@
1
- /* eslint-disable */
2
- export default {
3
- displayName: 'rx-computed',
4
- preset: '../../jest.preset.js',
5
- setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
6
- coverageDirectory: '../../coverage/packages/rx-computed',
7
- transform: {
8
- '^.+\\.(ts|mjs|js|html)$': [
9
- 'jest-preset-angular',
10
- {
11
- tsconfig: '<rootDir>/tsconfig.spec.json',
12
- stringifyContentPathRegex: '\\.(html|svg)$',
13
- },
14
- ],
15
- },
16
- transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
17
- snapshotSerializers: [
18
- 'jest-preset-angular/build/serializers/no-ng-attributes',
19
- 'jest-preset-angular/build/serializers/ng-snapshot',
20
- 'jest-preset-angular/build/serializers/html-comment',
21
- ],
22
- };
package/ng-package.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
- "dest": "../../dist/packages/rx-computed",
4
- "lib": {
5
- "entryFile": "src/index.ts"
6
- }
7
- }
package/project.json DELETED
@@ -1,50 +0,0 @@
1
- {
2
- "name": "rx-computed",
3
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
- "sourceRoot": "packages/rx-computed/src",
5
- "prefix": "jscutlery",
6
- "tags": [],
7
- "projectType": "library",
8
- "targets": {
9
- "build": {
10
- "executor": "@nx/angular:package",
11
- "outputs": ["{workspaceRoot}/dist/{projectRoot}"],
12
- "options": {
13
- "project": "packages/rx-computed/ng-package.json"
14
- },
15
- "configurations": {
16
- "production": {
17
- "tsConfig": "packages/rx-computed/tsconfig.lib.prod.json"
18
- },
19
- "development": {
20
- "tsConfig": "packages/rx-computed/tsconfig.lib.json"
21
- }
22
- },
23
- "defaultConfiguration": "production"
24
- },
25
- "test": {
26
- "executor": "@nx/jest:jest",
27
- "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
28
- "options": {
29
- "jestConfig": "packages/rx-computed/jest.config.ts"
30
- }
31
- },
32
- "lint": {
33
- "executor": "@nx/eslint:lint",
34
- "outputs": ["{options.outputFile}"]
35
- },
36
- "github": {
37
- "executor": "@jscutlery/semver:github",
38
- "options": {
39
- "notes": "{notes}",
40
- "tag": "{tag}"
41
- }
42
- },
43
- "npm": {
44
- "executor": "ngx-deploy-npm:deploy",
45
- "options": {
46
- "access": "public"
47
- }
48
- }
49
- }
50
- }
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export { rxComputed } from './lib/rx-computed';
@@ -1,153 +0,0 @@
1
- import {
2
- Component,
3
- EnvironmentInjector,
4
- Injector,
5
- runInInjectionContext,
6
- signal
7
- } from '@angular/core';
8
- import { TestBed } from '@angular/core/testing';
9
- import { NEVER, Observable, of, Subject, throwError } from 'rxjs';
10
- import { rxComputed } from './rx-computed';
11
-
12
- describe(rxComputed.name, () => {
13
- it('should return undefined as default initial value', () => {
14
- const { rxComputed } = setUp();
15
-
16
- const signal = rxComputed(() => NEVER);
17
-
18
- expect(signal()).toBeUndefined();
19
- });
20
-
21
- it('should return custom initial value', () => {
22
- const { rxComputed } = setUp();
23
-
24
- const signal = rxComputed(() => NEVER, { initialValue: null });
25
-
26
- expect(signal()).toBeNull();
27
- });
28
-
29
- it('should return emitted sync value', () => {
30
- const { rxComputed } = setUp();
31
-
32
- const signal = rxComputed(() => of(42));
33
-
34
- expect(signal()).toBe(42);
35
- });
36
-
37
- it('should return emitted async value', () => {
38
- const { rxComputed } = setUp();
39
-
40
- const subject = new Subject<number>();
41
-
42
- const signal = rxComputed(() => subject);
43
-
44
- subject.next(42);
45
-
46
- expect(signal()).toBe(42);
47
- });
48
-
49
- it('should throw error when observable throws error', () => {
50
- const { rxComputed } = setUp();
51
-
52
- const signal = rxComputed(() => throwError(() => new Error('💥 Oups!')));
53
-
54
- expect(() => signal()).toThrow('💥 Oups!');
55
- });
56
-
57
- it('should unsubscribe when dependency changes', () => {
58
- const { rxComputed, flushEffects } = setUp();
59
- const unsubscribe = jest.fn();
60
-
61
- const value = signal(0);
62
-
63
- rxComputed(
64
- () =>
65
- new Observable((observer) => {
66
- observer.next(value() * 2);
67
- return unsubscribe;
68
- })
69
- );
70
-
71
- /* Update dependency and flush effects... */
72
- value.set(1);
73
- flushEffects();
74
-
75
- /* ... and make sure that observable tear down is called. */
76
- expect(unsubscribe).toBeCalled();
77
- });
78
-
79
- describe('without injection context', () => {
80
- it('should throw when invoked without injection context', () => {
81
- const { rxComputed } = setUpWithoutInjectionContext();
82
- expect(() => rxComputed(() => NEVER)).toThrow(
83
- /rxComputed\(\) can only be used within an injection context/
84
- );
85
- });
86
-
87
- it('should be able to use a custom Injector', () => {
88
- const { rxComputed } = setUpWithoutInjectionContext();
89
-
90
- const envInjector = TestBed.inject(EnvironmentInjector);
91
-
92
- const signal = rxComputed(() => of(42), {
93
- injector: envInjector
94
- });
95
- expect(signal()).toEqual(42);
96
- });
97
- });
98
-
99
- function setUp() {
100
- const { flushEffects, runInTestingInjectionContext } = setUpSignalTesting();
101
-
102
- return {
103
- flushEffects,
104
- rxComputed<T>(...args: Parameters<typeof rxComputed<T>>) {
105
- const signal = runInTestingInjectionContext(() =>
106
- rxComputed(...args)
107
- );
108
-
109
- flushEffects();
110
-
111
- return signal;
112
- }
113
- };
114
- }
115
- });
116
-
117
- function setUpWithoutInjectionContext() {
118
- const { flushEffects } = setUpSignalTesting();
119
-
120
- return {
121
- flushEffects,
122
- rxComputed<T>(...args: Parameters<typeof rxComputed<T>>) {
123
- const signal = rxComputed(...args);
124
-
125
- flushEffects();
126
-
127
- return signal;
128
- }
129
- };
130
- }
131
-
132
-
133
- function setUpSignalTesting() {
134
- const injector = TestBed.inject(Injector);
135
- const fixture = TestBed.createComponent(NoopComponent);
136
-
137
- /* Inspiration: https://github.com/angular/angular/blob/06b498f67f2ad16bb465ef378bdb16da84e41a1c/packages/core/rxjs-interop/test/to_observable_spec.ts#LL30C25-L30C25 */
138
- return {
139
- flushEffects() {
140
- fixture.detectChanges();
141
- },
142
- runInTestingInjectionContext<T>(fn: () => T): T {
143
- return runInInjectionContext(injector, fn);
144
- }
145
- };
146
- }
147
-
148
- @Component({
149
- standalone: true,
150
- template: ''
151
- })
152
- class NoopComponent {
153
- }
@@ -1,47 +0,0 @@
1
- import {
2
- assertInInjectionContext,
3
- computed,
4
- effect,
5
- inject,
6
- Injector,
7
- signal,
8
- Signal,
9
- } from '@angular/core';
10
- import { pending, Suspense, suspensify } from '@jscutlery/operators';
11
- import { Observable } from 'rxjs';
12
-
13
- export function rxComputed<T, INITIAL_VALUE = T | null | undefined>(
14
- fn: () => Observable<T>,
15
- {
16
- initialValue,
17
- injector,
18
- }: { initialValue?: INITIAL_VALUE; injector?: Injector } = {}
19
- ): Signal<T | INITIAL_VALUE> {
20
- if (!injector) assertInInjectionContext(rxComputed);
21
- injector ??= inject(Injector);
22
-
23
- const sig = signal<Suspense<T | INITIAL_VALUE>>(pending);
24
-
25
- effect(
26
- (onCleanup) => {
27
- const sub = fn()
28
- .pipe(suspensify())
29
- .subscribe((value) => sig.set(value));
30
- onCleanup(() => sub.unsubscribe());
31
- },
32
- { allowSignalWrites: true, injector }
33
- );
34
-
35
- return computed(() => {
36
- const suspense = sig();
37
- if (suspense.hasError) {
38
- throw suspense.error;
39
- }
40
-
41
- if (suspense.hasValue) {
42
- return suspense.value;
43
- }
44
-
45
- return initialValue as INITIAL_VALUE;
46
- });
47
- }
package/src/test-setup.ts DELETED
@@ -1,8 +0,0 @@
1
- // @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment
2
- globalThis.ngJest = {
3
- testEnvironmentOptions: {
4
- errorOnUnknownElements: true,
5
- errorOnUnknownProperties: true,
6
- },
7
- };
8
- import 'jest-preset-angular/setup-jest';
package/tsconfig.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2022",
4
- "useDefineForClassFields": false,
5
- "forceConsistentCasingInFileNames": true,
6
- "strict": true,
7
- "noImplicitOverride": true,
8
- "noPropertyAccessFromIndexSignature": true,
9
- "noImplicitReturns": true,
10
- "noFallthroughCasesInSwitch": true
11
- },
12
- "files": [],
13
- "include": [],
14
- "references": [
15
- {
16
- "path": "./tsconfig.lib.json"
17
- },
18
- {
19
- "path": "./tsconfig.spec.json"
20
- }
21
- ],
22
- "extends": "../../tsconfig.base.json",
23
- "angularCompilerOptions": {
24
- "enableI18nLegacyMessageIdFormat": false,
25
- "strictInjectionParameters": true,
26
- "strictInputAccessModifiers": true,
27
- "strictTemplates": true
28
- }
29
- }
package/tsconfig.lib.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/out-tsc",
5
- "declaration": true,
6
- "declarationMap": true,
7
- "inlineSources": true,
8
- "types": []
9
- },
10
- "exclude": [
11
- "src/**/*.spec.ts",
12
- "src/test-setup.ts",
13
- "jest.config.ts",
14
- "src/**/*.test.ts"
15
- ],
16
- "include": ["src/**/*.ts"]
17
- }
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "./tsconfig.lib.json",
3
- "compilerOptions": {
4
- "declarationMap": false
5
- },
6
- "angularCompilerOptions": {
7
- "compilationMode": "partial"
8
- }
9
- }
@@ -1,16 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/out-tsc",
5
- "module": "commonjs",
6
- "target": "es2016",
7
- "types": ["jest", "node"]
8
- },
9
- "files": ["src/test-setup.ts"],
10
- "include": [
11
- "jest.config.ts",
12
- "src/**/*.test.ts",
13
- "src/**/*.spec.ts",
14
- "src/**/*.d.ts"
15
- ]
16
- }