Skip to content

Commit 10aa8cb

Browse files
manfredsteyerrainerhahnekamp
authored andcommitted
feat: make rxMutation standalone (#219) [backport v19]
This makes `rxMutation` a fully autonomous function which can also be used entirely independently of the SignalStore. It also exposes the following additional properties: - `isSuccess` - `hasValue` - `value`
1 parent 36a7576 commit 10aa8cb

File tree

11 files changed

+802
-68
lines changed

11 files changed

+802
-68
lines changed

apps/demo/src/app/app.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<a mat-list-item routerLink="/feature-factory">withFeatureFactory</a>
2828
<a mat-list-item routerLink="/conditional">withConditional</a>
2929
<a mat-list-item routerLink="/mutation">withMutation</a>
30+
<a mat-list-item routerLink="/rx-mutation">rxMutation (without Store)</a>
3031
</mat-nav-list>
3132
</mat-drawer>
3233
<mat-drawer-content>

apps/demo/src/app/counter-rx-mutation/counter-rx-mutation.css

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<h1>rxMutation (without Store)</h1>
2+
3+
<div class="counter">{{ counter() }}</div>
4+
5+
<ul>
6+
<li>isPending: {{ isPending() }}</li>
7+
<li>Status: {{ status() }}</li>
8+
<li>Error: {{ error() | json }}</li>
9+
<li>Value: {{ value() | json }}</li>
10+
<li>hasValue: {{ hasValue() | json }}</li>
11+
</ul>
12+
13+
<div>
14+
<button (click)="incrementCounter()" [disabled]="isPending()">
15+
Increment by 1
16+
</button>
17+
<button (click)="incrementBy13()" [disabled]="isPending()">
18+
Increment by 13
19+
</button>
20+
</div>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { concatOp, rxMutation } from '@angular-architects/ngrx-toolkit';
2+
import { CommonModule } from '@angular/common';
3+
import { Component, signal } from '@angular/core';
4+
import { delay, Observable, of, throwError } from 'rxjs';
5+
6+
export type Params = {
7+
value: number;
8+
};
9+
10+
@Component({
11+
selector: 'demo-counter-rx-mutation',
12+
imports: [CommonModule],
13+
templateUrl: './counter-rx-mutation.html',
14+
styleUrl: './counter-rx-mutation.css',
15+
})
16+
export class CounterRxMutation {
17+
private counterSignal = signal(0);
18+
19+
private increment = rxMutation({
20+
operation: (params: Params) => {
21+
return calcSum(this.counterSignal(), params.value);
22+
},
23+
operator: concatOp,
24+
onSuccess: (result) => {
25+
this.counterSignal.set(result);
26+
},
27+
onError: (error) => {
28+
console.error('Error occurred:', error);
29+
},
30+
});
31+
32+
// Expose signals for template
33+
protected counter = this.counterSignal.asReadonly();
34+
protected error = this.increment.error;
35+
protected isPending = this.increment.isPending;
36+
protected status = this.increment.status;
37+
protected value = this.increment.value;
38+
protected hasValue = this.increment.hasValue;
39+
40+
async incrementCounter() {
41+
const result = await this.increment({ value: 1 });
42+
if (result.status === 'success') {
43+
console.log('Success:', result.value);
44+
}
45+
if (result.status === 'error') {
46+
console.log('Error:', result.error);
47+
}
48+
if (result.status === 'aborted') {
49+
console.log('Operation aborted');
50+
}
51+
}
52+
53+
async incrementBy13() {
54+
await this.increment({ value: 13 });
55+
}
56+
}
57+
58+
function calcSum(a: number, b: number): Observable<number> {
59+
const result = a + b;
60+
if (b === 13) {
61+
return throwError(() => ({
62+
message: 'error due to bad luck!',
63+
a,
64+
b,
65+
result,
66+
}));
67+
}
68+
return of(result).pipe(delay(500));
69+
}

apps/demo/src/app/lazy-routes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,11 @@ export const lazyRoutes: Route[] = [
6868
(m) => m.CounterMutation,
6969
),
7070
},
71+
{
72+
path: 'rx-mutation',
73+
loadComponent: () =>
74+
import('./counter-rx-mutation/counter-rx-mutation').then(
75+
(m) => m.CounterRxMutation,
76+
),
77+
},
7178
];

libs/ngrx-toolkit/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export {
4242
export { emptyFeature, withConditional } from './lib/with-conditional';
4343
export { withFeatureFactory } from './lib/with-feature-factory';
4444

45-
export * from './lib/rx-mutation';
45+
export * from './lib/mutation/rx-mutation';
4646
export * from './lib/with-mutations';
4747
export { mapToResource, withResource } from './lib/with-resource';
4848

@@ -52,3 +52,5 @@ export {
5252
mergeOp,
5353
switchOp,
5454
} from './lib/flattening-operator';
55+
56+
export { rxMutation } from './lib/mutation/rx-mutation';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Signal } from '@angular/core';
2+
3+
export type MutationResult<Result> =
4+
| {
5+
status: 'success';
6+
value: Result;
7+
}
8+
| {
9+
status: 'error';
10+
error: unknown;
11+
}
12+
| {
13+
status: 'aborted';
14+
};
15+
16+
export type MutationStatus = 'idle' | 'pending' | 'error' | 'success';
17+
18+
export type Mutation<Parameter, Result> = {
19+
(params: Parameter): Promise<MutationResult<Result>>;
20+
status: Signal<MutationStatus>;
21+
value: Signal<Result | undefined>;
22+
isPending: Signal<boolean>;
23+
isSuccess: Signal<boolean>;
24+
error: Signal<unknown>;
25+
hasValue(): this is Mutation<Exclude<Parameter, undefined>, Result>;
26+
};

0 commit comments

Comments
 (0)