Skip to content

Commit

Permalink
feat(store): expose abort controller on state context
Browse files Browse the repository at this point in the history
  • Loading branch information
arturovt committed Nov 3, 2024
1 parent f22f4cb commit ffe76c5
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class HmrStateContextFactory<T, S> {
*/
public createStateContext(): StateContext<S> {
return {
abortController: new AbortController(),
dispatch: actions => this.store!.dispatch(actions),
getState: () => <S>this.store!.snapshot(),
setState: val => {
Expand Down
1 change: 1 addition & 0 deletions packages/store/src/internal/lifecycle-state-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export class LifecycleStateManager implements OnDestroy {
}

private _getStateContext(mappedStore: MappedStore): StateContext<any> {
// Question: abort controller is not gonna be available for lifecycle hooks.
return this._stateContextFactory.createStateContext(mappedStore.path);
}
}
15 changes: 8 additions & 7 deletions packages/store/src/internal/state-context-factory.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { getValue, setValue } from '@ngxs/store/plugins';
import { ExistingState, StateOperator, isStateOperator } from '@ngxs/store/operators';
import { Observable } from 'rxjs';
import type { Observable } from 'rxjs';

import { StateContext } from '../symbols';
import { StateOperations } from '../internal/internals';
import { InternalStateOperations } from '../internal/state-operations';
import type { StateContext } from '../symbols';
import { simplePatch } from './state-operators';
import type { StateOperations } from '../internal/internals';
import { InternalStateOperations } from '../internal/state-operations';

/**
* State Context factory class
* @ignore
*/
@Injectable({ providedIn: 'root' })
export class StateContextFactory {
constructor(private _internalStateOperations: InternalStateOperations) {}
private _internalStateOperations = inject(InternalStateOperations);

/**
* Create the state context
*/
createStateContext<T>(path: string): StateContext<T> {
createStateContext<T>(path: string, abortController?: AbortController): StateContext<T> {
const root = this._internalStateOperations.getRootStateOperations();

return {
abortController: abortController!,
getState(): T {
const currentAppState = root.getState();
return getState(currentAppState, path);
Expand Down
21 changes: 15 additions & 6 deletions packages/store/src/internal/state-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
mergeMap,
takeUntil,
finalize,
Observable
Observable,
fromEvent
} from 'rxjs';

import { NgxsConfig } from '../symbols';
Expand Down Expand Up @@ -345,10 +346,14 @@ export class StateFactory implements OnDestroy {
const { dispatched$ } = this._actions;
for (const actionType of Object.keys(actions)) {
const actionHandlers = actions[actionType].map(actionMeta => {
const cancelable = !!actionMeta.options.cancelUncompleted;
const abortController = new AbortController();
const cancellable = !!actionMeta.options.cancelUncompleted;

return (action: any) => {
const stateContext = this._stateContextFactory.createStateContext(path);
const stateContext = this._stateContextFactory.createStateContext(
path,
abortController
);

let result = instance[actionMeta.fn](stateContext, action);

Expand Down Expand Up @@ -384,12 +389,16 @@ export class StateFactory implements OnDestroy {
defaultIfEmpty(undefined)
);

if (cancelable) {
const notifier$ = dispatched$.pipe(ofActionDispatched(action));
result = result.pipe(takeUntil(notifier$));
if (cancellable) {
const cancelled = dispatched$.pipe(ofActionDispatched(action));
result = result.pipe(takeUntil(cancelled));
}

const aborted = fromEvent(abortController.signal, 'abort');

result = result.pipe(
takeUntil(aborted),

// Note that we use the `finalize` operator only when the action handler
// returns an observable. If the action handler is synchronous, we do not
// need to set the state context functions to `noop`, as the absence of a
Expand Down
2 changes: 2 additions & 0 deletions packages/store/src/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ export { StateOperator };
* State context provided to the actions in the state.
*/
export interface StateContext<T> {
abortController: AbortController;

/**
* Get the current state.
*/
Expand Down

0 comments on commit ffe76c5

Please sign in to comment.