Skip to content

Commit 86dae22

Browse files
docs(signals): add documentation for Scoped Events (#5033)
Closes #5013
1 parent e77453b commit 86dae22

File tree

1 file changed

+145
-0
lines changed
  • projects/www/src/app/pages/guide/signals/signal-store

1 file changed

+145
-0
lines changed

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

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,3 +458,148 @@ export class BookSearch {
458458
```
459459

460460
</ngrx-code-example>
461+
462+
## Scoped Events
463+
464+
By default, the `Dispatcher` and `Events` services operate in a global scope where all dispatched events are handled application-wide.
465+
In some cases, event handling should be isolated to a particular feature or component subtree.
466+
Typical examples include local state management scenarios where events should stay within a specific feature, or micro-frontend architectures where each remote module needs its own isolated event scope.
467+
To support this, the Events plugin provides a built-in mechanism for scoped events.
468+
469+
### Creating Local Scope
470+
471+
A new event scope can be created at a feature or component level by using the `provideDispatcher()` function.
472+
Any events dispatched inside this boundary will belong to the local scope unless explicitly forwarded.
473+
474+
When dispatching an event, the scope can be explicitly selected using the dispatch configuration:
475+
476+
- `self` (default): An event dispatched and handled only within the local scope.
477+
- `parent`: An event is forwarded to the parent dispatcher.
478+
- `global`: An event is forwarded to the global dispatcher.
479+
480+
<ngrx-code-example header="book-search.ts">
481+
482+
```ts
483+
// ... other imports
484+
import { provideDispatcher } from '@ngrx/signals/events';
485+
486+
@Component({
487+
// ... component config
488+
providers: [
489+
// 👇 Provide local `Dispatcher` and `Events` services
490+
// at the `BookSearch` injector level.
491+
provideDispatcher(),
492+
BookSearchStore,
493+
],
494+
})
495+
export class BookSearch {
496+
readonly store = inject(BookSearchStore);
497+
readonly dispatch = injectDispatch(bookSearchEvents);
498+
499+
constructor() {
500+
// 👇 Dispatch event to the local scope.
501+
this.dispatch.opened();
502+
}
503+
504+
changeQuery(query: string): void {
505+
// 👇 Dispatch event to the parent scope.
506+
this.dispatch({ scope: 'parent' }).queryChanged(query);
507+
}
508+
509+
triggerRefresh(): void {
510+
// 👇 Dispatch event to the global scope.
511+
this.dispatch({ scope: 'global' }).refreshTriggered();
512+
}
513+
}
514+
```
515+
516+
</ngrx-code-example>
517+
518+
Event flow within scopes follows a hierarchical visibility rule, which means that `Events` service receives events dispatched in their own scope and events dispatched in any parent scope, including the global scope.
519+
On the other hand, events dispatched locally are not visible to ancestor scopes.
520+
521+
<ngrx-docs-alert type="help">
522+
523+
When using `Dispatcher`, the scope can be provided as a second argument of the `dispatch` method.
524+
525+
```ts
526+
@Component({
527+
// ... component config
528+
providers: [provideDispatcher(), CounterStore],
529+
})
530+
export class Counter {
531+
readonly dispatcher = inject(Dispatcher);
532+
533+
increment(): void {
534+
this.dispatcher.dispatch(counterPageEvents.increment(), {
535+
scope: 'parent',
536+
});
537+
}
538+
539+
incrementBy(count: number): void {
540+
this.dispatcher.dispatch(counterPageEvents.incrementBy(count), {
541+
scope: 'global',
542+
});
543+
}
544+
}
545+
```
546+
547+
</ngrx-docs-alert>
548+
549+
### Scoped Events in Event Handlers
550+
551+
Scoped events can also be dispatched from event handlers. The Events plugin provides:
552+
553+
- `toScope`: Forwards a returned event to the specified scope.
554+
- `mapToScope`: RxJS operator that applies scope forwarding to all returned events within a handler.
555+
556+
<ngrx-code-example header="book-search-store.ts">
557+
558+
```ts
559+
// ... other imports
560+
import { mapToScope, toScope } from '@ngrx/signals/events';
561+
562+
export const BookSearchStore = signalStore(
563+
// ... other features
564+
withEventHandlers(
565+
(
566+
store,
567+
events = inject(Events),
568+
booksService = inject(BooksService)
569+
) => ({
570+
loadBooksByQuery$: events
571+
.on(bookSearchEvents.queryChanged)
572+
.pipe(
573+
switchMap(({ payload: query }) =>
574+
booksService.getByQuery(query).pipe(
575+
mapResponse({
576+
// 👇 Dispatch `loadedSuccess` to the current scope.
577+
next: (books) => booksApiEvents.loadedSuccess(books),
578+
// 👇 Dispatch `loadedFailure` to the global scope.
579+
error: (error: { message: string }) => [
580+
booksApiEvents.loadedFailure(error.message),
581+
toScope('global'),
582+
],
583+
})
584+
)
585+
)
586+
),
587+
loadBookById$: events.on(bookSearchEvents.bookSelected).pipe(
588+
exhaustMap(({ payload: bookId }) =>
589+
booksService.getById(bookId).pipe(
590+
mapResponse({
591+
next: (book) => booksApiEvents.loadedByIdSuccess(book),
592+
error: (error: { message: string }) =>
593+
booksApiEvents.loadedByIdFailure(error.message),
594+
}),
595+
// 👇 Dispatch all returned events to the parent scope.
596+
mapToScope('parent')
597+
)
598+
)
599+
),
600+
})
601+
)
602+
);
603+
```
604+
605+
</ngrx-code-example>

0 commit comments

Comments
 (0)