feat(signals): define SignalStore members as readonly
PR Checklist
Please check if your PR fulfills the following requirements:
- [x] The commit message follows our guidelines: https://github.com/ngrx/platform/blob/main/CONTRIBUTING.md#commit
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Documentation has been added / updated (for bug fixes / features)
PR Type
What kind of change does this PR introduce?
[x] Feature
What is the current behavior?
https://github.com/ngrx/platform/discussions/4712
What is the new behavior?
After these changes, the store and entities will become type-safe:
this.userStore.entities = signal([{ id: 1000, name: 'Vova' }]); // error
this.userStore.entityMap()[0] = { id: 2000, name: 'Dima' }; // error
this.userStore.entities()[0] = { id: 2000, name: 'Dima' }; // error
this.userStore.entities = signal([]); // error
this.userStore.loadAll = () => undefined; // error
Does this PR introduce a breaking change?
[x] Yes
[ ] No
If someone modifies the store or directly edits entities, they will need to fix these errors (or add just as any). However, this will help prevent future errors by enforcing type safety.
Also, I want to mention that it doesn't introduces "breaking changes" in logic, like it was with Object.freeze that was reverted https://github.com/ngrx/platform/issues/4683.
Other information
In my application, almost every type includes readonly to ensure type safety. I always know when this rule is broken—only when using the as keyword. It would be great to have similar safety in @ngrx/signals.
This PR provides the minimum type safety I want. Additionally, I suggest changing every instance like this:
SignalStoreFeature<Input, { state: {}; props: {}; methods: Methods }>
To:
{
readonly state: {};
readonly props: {};
readonly methods: Methods;
}
I can also implement this change if the idea is accepted.
Deploy Preview for ngrx-io canceled.
Built without sensitive environment variables
| Name | Link |
|---|---|
| Latest commit | fb54f1538543cb2e3b6a8ecaafbd1b5fdf5e88f1 |
| Latest deploy log | https://app.netlify.com/sites/ngrx-io/deploys/67c4b05c2d5b8e000837f732 |
This is a breaking change and cannot be considered before v20. I'll rename the PR with a precise title and it will be revisited before v20.
Regarding non-readonly SignalStore members, they could be useful in tests, if someone wants to replace a specific prop with a mocked one:
const myStore = TestBed.inject(MyStore);
myStore.mySignal = signal(10);
// ...
I don't expect anyone to do this in production code, so let's see if introducing this breaking change will be worth it.
We're going to postpone this change. To have a clean PR list, we're going to close this one.