platform icon indicating copy to clipboard operation
platform copied to clipboard

RFC(`@ngrx/signals`): Add `withProps` feature

Open markostanimirovic opened this issue 1 year ago • 7 comments

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

signals

Information

This feature request suggests adding another SignalStore base feature - withProps. It would allow static properties or observables to be defined as SignalStore members.

export const BooksStore = signalStore(
  withEntities<Book>(),
  withRequestStatus(),
  withProps(({ isFulfilled }) => ({
    fulfilled$: toObservable(isFulfilled).pipe(filter(Boolean)),
  })),
);

It can also be used for defining all dependencies in a single place:

export const MyStore = signalStore(
  withProps(() => ({
    service1: inject(Service1),
    service2: inject(Service2),
  })),
  withMethods(({ service1, service2 }) => ({
    method1() {
      service1.foo();
    },
    method2() {
      service2.bar();
    },
  }))
);

The withProps feature will be useful for library authors to reuse the same config across multiple features:

const SOURCE = Symbol('SOURCE');

function withSource(source: string) {
  return signalStoreFeature(withProps({ [SOURCE]: source }));
}

export const BooksStore = signalStore(
  withSource('BooksStore'),
  withDevtools(),
  withEvents({
    loadedSuccess: props<{ books: Book[] }>(),
    loadedFailure: props<{ error: string }>(),
  })
);

In this example, both withDevtools and withEvents would use the source from the withSource feature.

Signatures

The withProps feature would have 2 signatures:

  1. withProps({ foo: 'bar' })
  2. withProps(() => ({ foo: 'bar' }))

  • The withProps factory function would accept previously defined state signals, computed signals, and properties.
  • withComputed, withMethods, and withHooks would accept previously defined properties.

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

  • [X] Yes
  • [ ] No

markostanimirovic avatar Aug 22 '24 21:08 markostanimirovic

Yes, that would be an addition, we definitely should have. Just two questions:

  • Do we now have constraints for the property type, or can it be anything?
  • You stated that the factory function gets the existing slices and computeds. I guess it would also get the previous props? Why do we not pass the methods as well?

rainerhahnekamp avatar Aug 22 '24 22:08 rainerhahnekamp

As suggested something like this last week on discord when talking with @rainerhahnekamp. It would be super useful to open many new capabilities and to avoid hacks with Proxy applied on method in withMethods.

Are there any more details about limitations and where those props would be exposed? I'm hopping to have access to them in withComputed.

k3nsei avatar Aug 23 '24 08:08 k3nsei

I updated the RFC description with more details:

  • The withProps factory function would accept previously defined state signals, computed signals, and properties.
  • withComputed, withMethods, and withHooks would accept previously defined properties.

markostanimirovic avatar Aug 23 '24 09:08 markostanimirovic

This would be useful!

Can props be private the same way state/computed/methods are?

GuillaumeNury avatar Aug 23 '24 10:08 GuillaumeNury

IMO it makes a lot of sense when integrating with libs/things from outside of angular ecosystem, which don't fit any of existing withState, withComputed etc.

The only thing which bothers me is the name - withProps. The "property" word doesn't outline what is the key here - being static. How about withStatic?

ducin avatar Aug 23 '24 13:08 ducin

@ducin thats why I also proposed alternative name withExplicit

Edited: Ahhh it was in other discussion

k3nsei avatar Aug 23 '24 13:08 k3nsei

Hi guys, I have a legit use case for signals

Let's pretend I have one app state x (contained in a store, with its own computed and methods) But I also have n lazy features that might depend up to a certain point on this app state, but given they are lazy, I might never load them, so I don't include them eagerly in the main state.

However, if I load them, I need to take care of types and members being exposed upstream (app state). The idea is that I can build a core state, and create as many "modules" as my logic needs, and plug them lazily.

When I load the lazy feature n1, I'd like to have access to the same instance of the app state, react to changes in the state and "extend" with new computed and methods.

I was exploring signalStoreFeature, but these are intended to extend instances of a store ahead of time. This issue looks interesting, and it might be the solution I'm looking for.

Could you please confirm if this feature will satisfy the use case I mentioned? otherwise, I will need to look for alternatives. Thanks in advance

robmanganelly avatar Oct 11 '24 21:10 robmanganelly