platform icon indicating copy to clipboard operation
platform copied to clipboard

Expose more models to the public API: SignalStoreFeatureResult and InputSignalStore<Input>

Open GuillaumeNury opened this issue 11 months ago • 0 comments

Which @ngrx/* package(s) are relevant/related to the feature request?

signals

Information

In order to add more complexe features, it would be great to expose more models to the public API.

Here is an example of what I try to achieve

export const ExampleStore = signalStore(
  withState<ExampleState>({ field: 12 }),
  withMethods(
    (store) => ({
      log(text: string): void {
        console.log(text)
      },
    }),
  ),
  withTabVisibility({
    onTabVisible: (store) => store.log('tab is visible: ' + store.field())
  }),
});

It is possible but I used many unexposed models:

import { SignalStoreFeature, StateSignal } from '@ngrx/signals';
import type {
  EmptyFeatureResult,
  SignalStoreFeatureResult,
  SignalStoreSlices,
} from '@ngrx/signals/src/signal-store-models';

type Prettify<T> = {
  [K in keyof T]: T[K];
} & {};

type TabVisibilityTrigger<Input extends SignalStoreFeatureResult> = (
  store: Prettify<
    SignalStoreSlices<Input['state']> &
      Input['signals'] &
      Input['methods'] &
      StateSignal<Prettify<Input['state']>>
  >,
) => void;

export function withTabVisibility<
  Input extends SignalStoreFeatureResult,
>(options: {
  onTabVisible?: TabVisibilityTrigger<Input>;
  onTabHidden?: TabVisibilityTrigger<Input>;
}): SignalStoreFeature<Input, EmptyFeatureResult> {
  return (store) => {
    const inputStore = {
      ...store, // Cannot use [STATE_SIGNAL] because it's not exposed
      ...store.slices,
      ...store.signals,
      ...store.methods,
    };
    return {
      ...store,
      hooks: {
        onInit: () => {
          store.hooks.onInit?.();
          console.log('onInit');
          document.addEventListener('visibilitychange', () =>
            document.hidden
              ? options.onTabHidden?.(inputStore)
              : options.onTabVisible?.(inputStore),
          );
        },
      },
    };
  };
}

What would be marvelous would be to:

  • Expose EmptyFeatureResult and SignalStoreFeatureResult
  • Create a type SignalStoreFeatureInputStore<Input> (could be used by withMethods and withHooks)
  • Create a function createFeatureInputStore (could be used by withMethods and withHooks as well)

I could have written:

import { SignalStoreFeature, StateSignal, createFeatureInputStore, SignalStoreFeatureResult, EmptyFeatureResult, SignalStoreFeatureInputStore } from '@ngrx/signals';

type TabVisibilityTrigger<Input extends SignalStoreFeatureResult> = (
  store: SignalStoreFeatureInputStore<Input>,
) => void;

export function withTabVisibility<
  Input extends SignalStoreFeatureResult,
>(options: {
  onTabVisible?: TabVisibilityTrigger<Input>;
  onTabHidden?: TabVisibilityTrigger<Input>;
}): SignalStoreFeature<Input, EmptyFeatureResult> {
  return (store) => {
    const inputStore = createFeatureInputStore(store);
    return {
      ...store,
      hooks: {
        onInit: () => {
          store.hooks.onInit?.();
          console.log('onInit');
          document.addEventListener('visibilitychange', () =>
            document.hidden
              ? options.onTabHidden?.(inputStore)
              : options.onTabVisible?.(inputStore),
          );
        },
      },
    };
  };
}

Thanks for this awesome lib ❤️

Describe any alternatives/workarounds you're currently using

It is possible to add hooks with:

const withTabVisiblity = signalStoreFeature(
    {
      methods: type<Partial<{ onTabVisible(): void; onTabHidden(): void }>>(),
    }
    // ...
)

Then:

export const ExampleStore = signalStore(
  withState<ExampleState>({}),
  withMethods(
    (store) => ({
      onTabVisible(): void {
        console.log('tab is visible')
      },
    }),
  ),
  withTabVisibility(),

The onTabVisible method seems completely decoupled from the withTabVisibility feature.

I would be willing to submit a PR to fix this issue

  • [X] Yes
  • [ ] No

GuillaumeNury avatar Mar 06 '24 11:03 GuillaumeNury