Skip to content

Commit 8994d92

Browse files
feat(signals): allow returning an array of observables from withEffects (#5008)
1 parent c719d19 commit 8994d92

File tree

3 files changed

+64
-2
lines changed

3 files changed

+64
-2
lines changed

modules/signals/events/spec/with-effects.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,33 @@ describe('withEffects', () => {
135135
]);
136136
});
137137

138+
it('handles observables returned as an array', () => {
139+
const Store = signalStore(
140+
{ providedIn: 'root' },
141+
withEffects((_, events = inject(Events)) => [
142+
events.on(event1, event2).pipe(map(({ type }) => event3(type))),
143+
events.on(event3).pipe(tap(() => {})),
144+
])
145+
);
146+
147+
const events = TestBed.inject(Events);
148+
const dispatcher = TestBed.inject(Dispatcher);
149+
const emittedEvents: EventInstance<string, unknown>[] = [];
150+
151+
events.on().subscribe((event) => emittedEvents.push(event));
152+
TestBed.inject(Store);
153+
154+
dispatcher.dispatch(event1());
155+
dispatcher.dispatch(event2());
156+
157+
expect(emittedEvents).toEqual([
158+
{ type: 'event1' },
159+
{ type: 'event3', payload: 'event1' },
160+
{ type: 'event2' },
161+
{ type: 'event3', payload: 'event2' },
162+
]);
163+
});
164+
138165
it('dispatches an event with provided scope via toScope', () => {
139166
const Store = signalStore(
140167
{ providedIn: 'root' },

modules/signals/events/src/with-effects.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function withEffects<Input extends SignalStoreFeatureResult>(
4949
Input['methods'] &
5050
WritableStateSource<Prettify<Input['state']>>
5151
>
52-
) => Record<string, Observable<unknown>>
52+
) => Record<string, Observable<unknown>> | Observable<unknown>[]
5353
): SignalStoreFeature<Input, EmptyFeatureResult> {
5454
return signalStoreFeature(
5555
type<Input>(),

projects/www/src/app/pages/guide/signals/signal-store/events.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ function incrementSecond(): PartialStateUpdater<{ count2: number }> {
215215
## Performing Side Effects
216216

217217
Side effects are handled using the `withEffects` feature.
218-
This feature accepts a function that receives the store instance as an argument and returns a dictionary of effects.
218+
This feature accepts a function that receives the store instance as an argument and returns either a dictionary of effects or an array of effects.
219219
Each effect is defined as an observable that reacts to specific events using the `Events` service.
220220
This service provides the `on` method that returns an observable of dispatched events filtered by the specified event types.
221221
If an effect returns a new event, that event is automatically dispatched.
@@ -260,6 +260,41 @@ export const BookSearchStore = signalStore(
260260

261261
</ngrx-code-example>
262262

263+
<ngrx-docs-alert type="help">
264+
265+
In addition to the `Events` service, effects can be defined by listening to any other observable source.
266+
It's also possible to return an array of effects from the `withEffects` feature.
267+
268+
```ts
269+
// ... other imports
270+
import { exhaustMap, tap, timer } from 'rxjs';
271+
import { withEffects } from '@ngrx/signals/events';
272+
import { mapResponse } from '@ngrx/operators';
273+
import { BooksService } from './books-service';
274+
275+
export const BookSearchStore = signalStore(
276+
// ... other features
277+
withEffects((store, booksService = inject(BooksService)) => [
278+
timer(0, 30_000).pipe(
279+
exhaustMap(() =>
280+
booksService.getAll().pipe(
281+
mapResponse({
282+
next: (books) => booksApiEvents.loadedSuccess(books),
283+
error: (error: { message: string }) =>
284+
booksApiEvents.loadedFailure(error.message),
285+
})
286+
)
287+
)
288+
),
289+
events
290+
.on(booksApiEvents.loadedFailure)
291+
.pipe(tap(({ payload }) => console.error(payload))),
292+
])
293+
);
294+
```
295+
296+
</ngrx-docs-alert>
297+
263298
## Reading State
264299

265300
The Events plugin doesn’t change how the state is exposed or consumed.

0 commit comments

Comments
 (0)