@angular-architects/ngrx-toolkit 20.3.0 → 20.4.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.
@@ -2,9 +2,10 @@ import * as i0 from '@angular/core';
2
2
  import { Injectable, InjectionToken, signal, effect, inject, PLATFORM_ID, computed, isSignal, untracked, isDevMode as isDevMode$1, DestroyRef } from '@angular/core';
3
3
  import { watchState, getState, signalStoreFeature, withMethods, withHooks, patchState as patchState$1, withState, withComputed, withProps, withLinkedState } from '@ngrx/signals';
4
4
  import { isPlatformBrowser, isPlatformServer } from '@angular/common';
5
- import { Subject, switchMap, mergeMap, concatMap, exhaustMap, defer, tap, catchError, EMPTY, finalize } from 'rxjs';
5
+ import { Subject, switchMap, mergeMap, concatMap, exhaustMap, defer, tap, catchError, EMPTY, finalize, filter, map } from 'rxjs';
6
6
  import { removeEntity, setAllEntities, updateEntity, addEntity } from '@ngrx/signals/entities';
7
7
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
+ import { HttpClient, HttpEventType, HttpResponse } from '@angular/common/http';
8
9
 
9
10
  const DEVTOOLS_FEATURE = Symbol('DEVTOOLS_FEATURE');
10
11
  function createDevtoolsFeature(options) {
@@ -341,7 +342,7 @@ Enable automatic indexing via withDevTools('${storeName}', { indexNames: true })
341
342
  }, {});
342
343
  this.#currentState = Object.entries(this.#currentState).reduce((newState, [storeName, state]) => {
343
344
  if (storeName !== name) {
344
- newState[name] = state;
345
+ newState[storeName] = state;
345
346
  }
346
347
  return newState;
347
348
  }, {});
@@ -349,18 +350,31 @@ Enable automatic indexing via withDevTools('${storeName}', { indexNames: true })
349
350
  tracker.removeStore(id);
350
351
  }
351
352
  }
352
- renameStore(oldName, newName) {
353
- const storeNames = Object.values(this.#stores).map((store) => store.name);
354
- const id = throwIfNull(Object.keys(this.#stores).find((id) => this.#stores[id].name === oldName));
355
- if (storeNames.includes(newName)) {
353
+ /**
354
+ * Renames a store identified by its internal id. If the store has already
355
+ * been removed (e.g. due to component destruction), this is a no-op.
356
+ */
357
+ renameStore(id, newName) {
358
+ const storeEntry = this.#stores[id];
359
+ if (!storeEntry) {
360
+ return;
361
+ }
362
+ const oldName = storeEntry.name;
363
+ if (oldName === newName) {
364
+ return;
365
+ }
366
+ const otherStoreNames = Object.entries(this.#stores)
367
+ .filter(([entryId]) => entryId !== id)
368
+ .map(([, s]) => s.name);
369
+ if (otherStoreNames.includes(newName)) {
356
370
  throw new Error(`NgRx Toolkit/DevTools: cannot rename from ${oldName} to ${newName}. ${newName} is already assigned to another SignalStore instance.`);
357
371
  }
358
- this.#stores = Object.entries(this.#stores).reduce((newStore, [id, value]) => {
359
- if (value.name === oldName) {
360
- newStore[id] = { ...value, name: newName };
372
+ this.#stores = Object.entries(this.#stores).reduce((newStore, [entryId, value]) => {
373
+ if (entryId === id) {
374
+ newStore[entryId] = { ...value, name: newName };
361
375
  }
362
376
  else {
363
- newStore[id] = value;
377
+ newStore[entryId] = value;
364
378
  }
365
379
  return newStore;
366
380
  }, {});
@@ -405,7 +419,7 @@ function withDevtools(name, ...features) {
405
419
  // TODO: use withProps and symbols
406
420
  return {
407
421
  [renameDevtoolsMethodName]: (newName) => {
408
- syncer.renameStore(name, newName);
422
+ syncer.renameStore(id, newName);
409
423
  },
410
424
  [uniqueDevtoolsId]: () => id,
411
425
  };
@@ -1811,47 +1825,73 @@ const exhaustOp = {
1811
1825
  *
1812
1826
  * The `operation` is the only mandatory option.
1813
1827
  *
1828
+ * The returned mutation can be called as an async function and returns a Promise.
1829
+ * This promise informs about whether the mutation was successful, failed, or aborted
1830
+ * (due to switchMap or exhaustMap semantics).
1831
+ *
1832
+ * The mutation also provides several Signals such as error, status or isPending (see below).
1833
+ *
1834
+ * Example usage without Store:
1835
+ *
1814
1836
  * ```typescript
1815
- * export type Params = {
1816
- * value: number;
1817
- * };
1837
+ * const counterSignal = signal(0);
1818
1838
  *
1819
- * export const CounterStore = signalStore(
1820
- * { providedIn: 'root' },
1821
- * withState({ counter: 0 }),
1822
- * withMutations((store) => ({
1823
- * increment: rxMutation({
1824
- * operation: (params: Params) => {
1825
- * return calcSum(store.counter(), params.value);
1826
- * },
1827
- * operator: concatOp,
1828
- * onSuccess: (result) => {
1829
- * console.log('result', result);
1830
- * patchState(store, { counter: result });
1831
- * },
1832
- * onError: (error) => {
1833
- * console.error('Error occurred:', error);
1834
- * },
1835
- * }),
1836
- * })),
1837
- * );
1839
+ * const increment = rxMutation({
1840
+ * operation: (param: Param) => {
1841
+ * return calcSum(this.counterSignal(), param.value);
1842
+ * },
1843
+ * operator: concatOp,
1844
+ * onSuccess: (result) => {
1845
+ * this.counterSignal.set(result);
1846
+ * },
1847
+ * onError: (error) => {
1848
+ * console.error('Error occurred:', error);
1849
+ * },
1850
+ * });
1851
+ *
1852
+ * const error = increment.error;
1853
+ * const isPending = increment.isPending;
1854
+ * const status = increment.status;
1855
+ * const value = increment.value;
1856
+ * const hasValue = increment.hasValue;
1857
+ *
1858
+ * async function incrementCounter() {
1859
+ * const result = await increment({ value: 1 });
1860
+ * if (result.status === 'success') {
1861
+ * console.log('Success:', result.value);
1862
+ * }
1863
+ * if (result.status === 'error') {
1864
+ * console.log('Error:', result.error);
1865
+ * }
1866
+ * if (result.status === 'aborted') {
1867
+ * console.log('Operation aborted');
1868
+ * }
1869
+ * }
1838
1870
  *
1839
1871
  * function calcSum(a: number, b: number): Observable<number> {
1840
- * return of(a + b);
1872
+ * return of(result).pipe(delay(500));
1841
1873
  * }
1842
1874
  * ```
1843
1875
  *
1844
1876
  * @param options
1845
- * @returns
1877
+ * @returns the actual mutation function along tracking data as properties/methods
1846
1878
  */
1847
- function rxMutation(options) {
1879
+ function rxMutation(optionsOrOperation) {
1848
1880
  const inputSubject = new Subject();
1881
+ const options = typeof optionsOrOperation === 'function'
1882
+ ? { operation: optionsOrOperation }
1883
+ : optionsOrOperation;
1849
1884
  const flatteningOp = options.operator ?? concatOp;
1850
1885
  const destroyRef = options.injector?.get(DestroyRef) ?? inject(DestroyRef);
1851
1886
  const callCount = signal(0, ...(ngDevMode ? [{ debugName: "callCount" }] : []));
1852
1887
  const errorSignal = signal(undefined, ...(ngDevMode ? [{ debugName: "errorSignal" }] : []));
1853
1888
  const idle = signal(true, ...(ngDevMode ? [{ debugName: "idle" }] : []));
1854
1889
  const isPending = computed(() => callCount() > 0, ...(ngDevMode ? [{ debugName: "isPending" }] : []));
1890
+ const value = signal(undefined, ...(ngDevMode ? [{ debugName: "value" }] : []));
1891
+ const isSuccess = computed(() => !idle() && !isPending() && !errorSignal(), ...(ngDevMode ? [{ debugName: "isSuccess" }] : []));
1892
+ const hasValue = function () {
1893
+ return typeof value() !== 'undefined';
1894
+ };
1855
1895
  const status = computed(() => {
1856
1896
  if (idle()) {
1857
1897
  return 'idle';
@@ -1866,7 +1906,6 @@ function rxMutation(options) {
1866
1906
  }, ...(ngDevMode ? [{ debugName: "status" }] : []));
1867
1907
  const initialInnerStatus = 'idle';
1868
1908
  let innerStatus = initialInnerStatus;
1869
- let lastResult;
1870
1909
  inputSubject
1871
1910
  .pipe(flatteningOp.rxJsOperator((input) => defer(() => {
1872
1911
  callCount.update((c) => c + 1);
@@ -1875,10 +1914,11 @@ function rxMutation(options) {
1875
1914
  options.onSuccess?.(result, input.param);
1876
1915
  innerStatus = 'success';
1877
1916
  errorSignal.set(undefined);
1878
- lastResult = result;
1917
+ value.set(result);
1879
1918
  }), catchError((error) => {
1880
1919
  options.onError?.(error, input.param);
1881
1920
  errorSignal.set(error);
1921
+ value.set(undefined);
1882
1922
  innerStatus = 'error';
1883
1923
  return EMPTY;
1884
1924
  }), finalize(() => {
@@ -1886,7 +1926,7 @@ function rxMutation(options) {
1886
1926
  if (innerStatus === 'success') {
1887
1927
  input.resolve({
1888
1928
  status: 'success',
1889
- value: lastResult,
1929
+ value: value(),
1890
1930
  });
1891
1931
  }
1892
1932
  else if (innerStatus === 'error') {
@@ -1923,6 +1963,9 @@ function rxMutation(options) {
1923
1963
  mutation.status = status;
1924
1964
  mutation.isPending = isPending;
1925
1965
  mutation.error = errorSignal;
1966
+ mutation.value = value;
1967
+ mutation.hasValue = hasValue;
1968
+ mutation.isSuccess = isSuccess;
1926
1969
  return mutation;
1927
1970
  }
1928
1971
 
@@ -2060,9 +2103,107 @@ function mapToResource(store, name) {
2060
2103
  };
2061
2104
  }
2062
2105
 
2106
+ /**
2107
+ * Creates an HTTP mutation.
2108
+ *
2109
+ * ```typescript
2110
+ * export type Params = {
2111
+ * value: number;
2112
+ * };
2113
+ *
2114
+ * export type CounterResponse = {
2115
+ * // httpbin.org echos the request using the
2116
+ * // json property
2117
+ * json: { counter: number };
2118
+ * };
2119
+ *
2120
+ * const simpleSaveUser = httpMutation({
2121
+ * request: (userData: AddUserEntry) => ({
2122
+ * url: 'api/users',
2123
+ * body: userData,
2124
+ * }),
2125
+ * parse: Boolean,
2126
+ * })
2127
+ *
2128
+ * const saveUser = httpMutation({
2129
+ * request: (p: Params) => ({
2130
+ * url: `https://httpbin.org/post`,
2131
+ * method: 'POST',
2132
+ * body: { counter: p.value },
2133
+ * headers: { 'Content-Type': 'application/json' },
2134
+ * }),
2135
+ * onSuccess: (response: CounterResponse) => {
2136
+ * console.log('Counter sent to server:', response);
2137
+ * },
2138
+ * onError: (error) => {
2139
+ * console.error('Failed to send counter:', error);
2140
+ * },
2141
+ * });
2142
+ *
2143
+ * const result = await this.saveUser({ value: 17 });
2144
+ * if (result.status === 'success') {
2145
+ * console.log('Successfully saved to server:', result.value);
2146
+ * }
2147
+ * else if (result.status === 'error') {
2148
+ * console.log('Failed to save:', result.error);
2149
+ * }
2150
+ * else {
2151
+ * console.log('Operation aborted');
2152
+ * }
2153
+ * ```
2154
+ *
2155
+ * @param options The options for the HTTP mutation.
2156
+ * @returns The HTTP mutation.
2157
+ */
2158
+ function httpMutation(optionsOrRequest) {
2159
+ const httpClient = inject(HttpClient);
2160
+ const options = typeof optionsOrRequest === 'function'
2161
+ ? { request: optionsOrRequest }
2162
+ : optionsOrRequest;
2163
+ const parse = options.parse ?? ((raw) => raw);
2164
+ const uploadProgress = signal(undefined, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
2165
+ const downloadProgress = signal(undefined, ...(ngDevMode ? [{ debugName: "downloadProgress" }] : []));
2166
+ const headers = signal(undefined, ...(ngDevMode ? [{ debugName: "headers" }] : []));
2167
+ const statusCode = signal(undefined, ...(ngDevMode ? [{ debugName: "statusCode" }] : []));
2168
+ const mutation = rxMutation({
2169
+ ...options,
2170
+ operation: (param) => {
2171
+ const httpRequest = options.request(param);
2172
+ return defer(() => {
2173
+ uploadProgress.set(undefined);
2174
+ downloadProgress.set(undefined);
2175
+ headers.set(undefined);
2176
+ statusCode.set(undefined);
2177
+ return httpClient
2178
+ .request(httpRequest.method, httpRequest.url, {
2179
+ ...httpRequest,
2180
+ observe: 'events',
2181
+ responseType: 'json',
2182
+ })
2183
+ .pipe(tap((response) => {
2184
+ if (response.type === HttpEventType.UploadProgress) {
2185
+ uploadProgress.set(response);
2186
+ }
2187
+ else if (response.type === HttpEventType.DownloadProgress) {
2188
+ downloadProgress.set(response);
2189
+ }
2190
+ }), filter((event) => event instanceof HttpResponse), tap((response) => {
2191
+ headers.set(response.headers);
2192
+ statusCode.set(response.status.toString());
2193
+ }), map((event) => parse(event.body)));
2194
+ });
2195
+ },
2196
+ });
2197
+ mutation.uploadProgress = uploadProgress;
2198
+ mutation.downloadProgress = downloadProgress;
2199
+ mutation.statusCode = statusCode;
2200
+ mutation.headers = headers;
2201
+ return mutation;
2202
+ }
2203
+
2063
2204
  /**
2064
2205
  * Generated bundle index. Do not edit.
2065
2206
  */
2066
2207
 
2067
- export { capitalize, concatOp, createEffects, createPageArray, createReducer, deriveCallStateKeys, emptyFeature, exhaustOp, firstPage, getCallStateKeys, getCollectionArray, getDataServiceKeys, getUndoRedoKeys, gotoPage, mapToResource, mergeOp, nextPage, noPayload, patchState, payload, previousPage, provideDevtoolsConfig, renameDevtoolsName, rxMutation, setError, setLoaded, setLoading, setMaxPageNavigationArrayItems, setPageSize, setResetState, switchOp, updateState, withCallState, withConditional, withDataService, withDevToolsStub, withDevtools, withDisabledNameIndices, withFeatureFactory, withGlitchTracking, withImmutableState, withIndexedDB, withIndexedDB as withIndexeddb, withLocalStorage, withMapper, withMutations, withPagination, withRedux, withReset, withResource, withSessionStorage, withStorageSync, withUndoRedo };
2208
+ export { capitalize, concatOp, createEffects, createPageArray, createReducer, deriveCallStateKeys, emptyFeature, exhaustOp, firstPage, getCallStateKeys, getCollectionArray, getDataServiceKeys, getUndoRedoKeys, gotoPage, httpMutation, mapToResource, mergeOp, nextPage, noPayload, patchState, payload, previousPage, provideDevtoolsConfig, renameDevtoolsName, rxMutation, setError, setLoaded, setLoading, setMaxPageNavigationArrayItems, setPageSize, setResetState, switchOp, updateState, withCallState, withConditional, withDataService, withDevToolsStub, withDevtools, withDisabledNameIndices, withFeatureFactory, withGlitchTracking, withImmutableState, withIndexedDB, withIndexedDB as withIndexeddb, withLocalStorage, withMapper, withMutations, withPagination, withRedux, withReset, withResource, withSessionStorage, withStorageSync, withUndoRedo };
2068
2209
  //# sourceMappingURL=angular-architects-ngrx-toolkit.mjs.map