Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signalState is not creating DeepSignal property signals when the value object is an instance of a class #4604

Open
2 tasks
vtachkov opened this issue Nov 22, 2024 · 2 comments

Comments

@vtachkov
Copy link

Which @ngrx/* package(s) are the source of the bug?

signals

Minimal reproduction of the bug/regression with instructions

Using signalState, create a new store where the type of the value in the store is a class, and the value is set to an instance of that class. The documentation states that when the value in the store is an object, signals will be created for each property on the object (DeepSignal). But in the case where the object is a class instance, signals are not being created for each property, they are undefined.

If this isn't supported, that's one thing, but TypeScript still types each property on the value as a signal.

Minimal reproduction: https://stackblitz.com/edit/typescript-dxfawb?file=index.ts

Expected behavior

Signals are created for each property on the signalStore value when the value is an instance of a class.

Versions of NgRx, Angular, Node, affected browser(s) and operating system(s)

NgRx: 18+

Other information

No response

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

  • Yes
  • No
@rainerhahnekamp
Copy link
Contributor

Hi @vtachkov, I haven’t found a solution yet. I’ve shared my PR with the experiments I’ve tried so far. If you or anyone else discovers a solution, your input would be greatly appreciated!

@rainerhahnekamp rainerhahnekamp removed their assignment Nov 22, 2024
@rainerhahnekamp
Copy link
Contributor

Here is the link to the TypeScript Playground which shows the current status. it is not working btw. Any help is highly appreciated.

For your convenience, here's the code:

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

export type IsRecord<T> = T extends object
  ? T extends unknown[]
    ? false
    : T extends Set<unknown>
    ? false
    : T extends Map<unknown, unknown>
    ? false
    : T extends Function
    ? false
    : T extends { prototype: unknown } // check for constructor did not work
    ? false
    : T extends { [key: string]: unknown }
    ? true
    : false
  : false;

export type IsUnknownRecord<T> = string extends keyof T
  ? true
  : number extends keyof T
  ? true
  : false;

export type IsKnownRecord<T> = IsRecord<T> extends true
  ? IsUnknownRecord<T> extends true
    ? false
    : true
  : false;

export type OmitPrivate<T> = {
  [K in keyof T as K extends `_${string}` ? never : K]: T[K];
};

type NgSignal<T> = {set(value: T): void, update: (updateFn: (value: T) => T) => void} & (() => T)

// An extended Signal type that enables the correct typing
// of nested signals with the `name` or `length` key.
export interface Signal<T> extends NgSignal<T> {
  name: unknown;
  length: unknown;
}

export type DeepSignal<T> = Signal<T> &
  (IsKnownRecord<T> extends true
    ? Readonly<{
        [K in keyof T]: IsKnownRecord<T[K]> extends true
          ? DeepSignal<T[K]>
          : Signal<T[K]>;
      }>
    : unknown);

declare function signalState<T>(value: T): DeepSignal<T>;

class User {
  id = 0;
  name = 'Konrad';
}

const state = {
  user: new User(),
  id: 1,
  address: { city: 'Wien', zip: '1040' },
};

type AssertFalse<T extends false> = T;
type AssertTrue<T extends true> = T;
type IsType<Actual, Expected> = Expected extends Actual ? Actual extends Expected ? true : false;

const store = signalState(state);

type T1 = AssertFalse<IsRecord<User>>;
type T2 = AssertFalse<IsRecord<typeof state.id>>;
type T3 = AssertTrue<IsRecord<typeof state.address>>;
type T4 = AssertTrue<IsType<typeof store.address, DeepSignal<{city: string, zip: string}>>;
type T5 = AssertTrue<IsType<typeof store.user, Signal<User>>;
type T6 = AssertFalse<IsType<typeof store.user, DeepSignal<User>>; // fails because it is also true

https://www.typescriptlang.org/play/?#code/KYDwDg9gTgLgBDAnmYcAKVgxgSwGaIA8AKgHxwC8cA3nANoDScOAdnANbCIR5zEC6ALj6N+cAL5wAZDXEBuAFALQkWAmSoAkgGcASsADG0ACYlyVYnFAxgLY9rgQARgCtDMBXDgB+PlZA2dg4ArizsLBAA7ix0-J5ePnB4AIYANtrA8V7Clta29nAAyliEoeFRLKRZiSnpmQlwOf6BBQCyyWClYRHRADRwZT2V1b61GdVNeUFwAGKhBrgQLCNJaeMNkwH5DrRgUBAwBxrCgxUScAD0F3AGABaG7EnQN0vaMFDBC8-GOMZwEfBItB2CsxvUEpsWjt6JxEMI3lBWABzIQDbpncQrd7BcHZVZ1eLCMGKZTgaDwJAoOA6ACq6Oi+iMUFMZEocARyOa2w4XB4fHivmx4OELGCAFsnMAoFzprC+cQBQgPsL8RkSSpyeoqToGENGSYzGydPrmYapgUhYrafSWCaWeRzQ5LQ1RmtcY0lTjCargOqyWpKagAPJinAwDA4ABuyRshqo1HijGYbDlvEsyQcTEdcAABgB9AAk1A5LCR4hziRYwEjUo9DFRxFEinkSkDcAAckjCjgkSw0nGaBkYAAKaOpHE5ACUwkjEF+-WCYGMMeAwmHi+XNhmLDXY4nfEnlHIxEPFHIs9+khkw+Hp+Pk6UVzgAEE2ObgH9u720lrUDBbjGVh9k4qTAE69wvFAmALFqyIKE+fJVm8H7sj2fbpHAkRhrcCAQTmfZisAFbPDmoGlv+FawgAdKSqjwKwNhQCkBioF+6Fmls0ydmx-asgmXgEauaLlNEiheGRSL-icNrNkoGoBhocAACLAMAYA8akA4aYaUjxMOOp6oYBqstmzoJL4+jJMYSypEQ-ENAkSasDy3BpqiBkVHaJCiA6nEWsq1QOYkKlqdpjb1lUQUbEUaG8T5YkOeIkUQsJQyTiSxiGKkySYEk8yLGw2ixakhQwCuZijmk+4nsIIXqcVZgkgY2XaA4NIZNK9m-GyAAMCWCWyADkDBLFAVmDbJChGCwbzsmVNhsvZwQdSKwCRHA7VSrevTxL8wgAIw7V4VnGJgrXCLQBhhnCcCDQA6jgtiDf0ABeOBgMIg37T1AAsPWDRIO0tgobbPq1UowDMbokDKBRguYfCKKD4OwMQyow6ZyoI8QSOKToxAaIQz4LMEaT9AAouA7gfgjlMoAsKHZsTMCk6kiTM6zsMOHT1N-IKyoesSSjTbNbzQKgVBFd+JXzcAw5vCu6WtopxD7WyYMdZD0PGkZpqbVApCkLjVLEAATOrKNa3UhA60ypiBnyCs2FRvyG8bqDEAAzBbmtoziNt6Lr9saI7stUSdZ3aG7IMqz9PsQ37wABwTKCEA7vBi5g4fGKdYHaP0dXadQV1IPC7zIq971l4ipZJUbMcmwArPHqPo-jhPp3N4tUctUr9Np+vR22xAAGwt1bGTJx3IcZ4cWe91ABeqfV0uEIPRuXNcKQ4BhkoGMkvfMPRDhrBAnqZEAA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants