Skip to content

@ngrx/signal 20 patchState removes deepSignal properties when partial object is passed #4907

@obraud

Description

@obraud

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

signals

Minimal reproduction of the bug/regression with instructions

Hello!
In NgRx 20, using patchState on a deepSignal with an object missing required properties will actually remove those properties from state, resulting in errors at runtime when reading values from those missing signals.

Example:

interface State {
  loader: boolean;
  config: {
    isAdmin: boolean;
    name: string | undefined;
  };
}

MyStore = signalStore(
  withState<State>({
    loader: true,
    config: {
      isAdmin: false,
      name: 'World',
    },
  }),
  withMethods((store) => ({
    patch() {
      const config = { isAdmin: true } as State['config'];
      patchState(store, { config });
    },
  }))
)

Here, calling store.patch() will actually remove name from config. Then any call to store.config.name() will result in the following error TypeError: ctx.store.config.name is not a function. In NgRx 19, store.config.name() would juste return undefined.

Here is a stackblitz for reproduction with NgRX 20. For comparison, the same repo with NgRx 19.

Expected behavior

I understand this is an edge case as it should not happen with strict typing. I am also not sure if this is intended or not,
I would expect deepSignal property structure not to evolve depending on how its patched at runtime?

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

NgRx: 20.0.0
Angular: 20.1.3
Node: 22.17.1
Browser: Firefox, Chrome
Windows

Other information

No response

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

  • Yes
  • No

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions