diff --git a/modules/signals/spec/types/signal-store.types.spec.ts b/modules/signals/spec/types/signal-store.types.spec.ts index 543cd7d74b..415086dc9e 100644 --- a/modules/signals/spec/types/signal-store.types.spec.ts +++ b/modules/signals/spec/types/signal-store.types.spec.ts @@ -781,6 +781,58 @@ describe('signalStore', () => { expectSnippet(snippet + `store.log(10);`).toFail(); }); + it('omits private store members from the public instance', () => { + const snippet = ` + const CounterStore = signalStore( + withState({ count1: 0, _count2: 0 }), + withComputed(({ count1, _count2 }) => ({ + _doubleCount1: computed(() => count1() * 2), + doubleCount2: computed(() => _count2() * 2), + })), + withMethods(() => ({ + increment1() {}, + _increment2() {}, + })), + withHooks({ + onInit({ increment1, _increment2 }) { + increment1(); + _increment2(); + }, + }) + ); + + const store = new CounterStore(); + `; + + expectSnippet(snippet).toSucceed(); + + expectSnippet(snippet).toInfer( + 'store', + '{ count1: Signal; doubleCount2: Signal; increment1: () => void; } & StateSource<{ count1: number; }>' + ); + }); + + it('prevents private state slices from being updated from the outside', () => { + const snippet = ` + const CounterStore = signalStore( + { protectedState: false }, + withState({ count1: 0, _count2: 0 }), + ); + + const store = new CounterStore(); + `; + + expectSnippet(` + ${snippet} + patchState(store, { count1: 1 }); + `).toSucceed(); + + expectSnippet(` + ${snippet} + patchState(store, { count1: 1, _count2: 1 }); + `).toFail(/'_count2' does not exist in type/); + }); + describe('custom features', () => { const baseSnippet = ` function withX() { diff --git a/modules/signals/src/signal-store.ts b/modules/signals/src/signal-store.ts index 5ea12031cf..968ac78ef0 100644 --- a/modules/signals/src/signal-store.ts +++ b/modules/signals/src/signal-store.ts @@ -7,20 +7,24 @@ import { SignalStoreFeatureResult, StateSignals, } from './signal-store-models'; -import { Prettify } from './ts-helpers'; +import { OmitPrivate, Prettify } from './ts-helpers'; type SignalStoreConfig = { providedIn?: 'root'; protectedState?: boolean }; type SignalStoreMembers = Prettify< - StateSignals & - FeatureResult['computed'] & - FeatureResult['methods'] + OmitPrivate< + StateSignals & + FeatureResult['computed'] & + FeatureResult['methods'] + > >; export function signalStore( f1: SignalStoreFeature -): Type & StateSource>>; +): Type< + SignalStoreMembers & StateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -28,7 +32,7 @@ export function signalStore< >( f1: SignalStoreFeature, f2: SignalStoreFeature<{} & F1, F2> -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -38,7 +42,7 @@ export function signalStore< f1: SignalStoreFeature, f2: SignalStoreFeature<{} & F1, F2>, f3: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -50,7 +54,7 @@ export function signalStore< f2: SignalStoreFeature<{} & F1, F2>, f3: SignalStoreFeature, f4: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -64,7 +68,7 @@ export function signalStore< f3: SignalStoreFeature, f4: SignalStoreFeature, f5: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -80,7 +84,7 @@ export function signalStore< f4: SignalStoreFeature, f5: SignalStoreFeature, f6: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -98,7 +102,7 @@ export function signalStore< f5: SignalStoreFeature, f6: SignalStoreFeature, f7: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -118,7 +122,7 @@ export function signalStore< f6: SignalStoreFeature, f7: SignalStoreFeature, f8: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -148,7 +152,7 @@ export function signalStore< f7: SignalStoreFeature, f8: SignalStoreFeature, f9: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -181,7 +185,7 @@ export function signalStore< f8: SignalStoreFeature, f9: SignalStoreFeature, f10: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -217,7 +221,7 @@ export function signalStore< f9: SignalStoreFeature, f10: SignalStoreFeature, f11: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -262,7 +266,7 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11, F12 > -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -313,7 +317,7 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12, F13 > -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -370,7 +374,7 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13, F14 > -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -433,12 +437,14 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13 & F14, F15 > -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore( config: { providedIn?: 'root'; protectedState?: true }, f1: SignalStoreFeature -): Type & StateSource>>; +): Type< + SignalStoreMembers & StateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -447,7 +453,7 @@ export function signalStore< config: { providedIn?: 'root'; protectedState?: true }, f1: SignalStoreFeature, f2: SignalStoreFeature<{} & F1, F2> -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -458,7 +464,7 @@ export function signalStore< f1: SignalStoreFeature, f2: SignalStoreFeature<{} & F1, F2>, f3: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -471,7 +477,7 @@ export function signalStore< f2: SignalStoreFeature<{} & F1, F2>, f3: SignalStoreFeature, f4: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -486,7 +492,7 @@ export function signalStore< f3: SignalStoreFeature, f4: SignalStoreFeature, f5: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -503,7 +509,7 @@ export function signalStore< f4: SignalStoreFeature, f5: SignalStoreFeature, f6: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -522,7 +528,7 @@ export function signalStore< f5: SignalStoreFeature, f6: SignalStoreFeature, f7: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -543,7 +549,7 @@ export function signalStore< f6: SignalStoreFeature, f7: SignalStoreFeature, f8: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -574,7 +580,7 @@ export function signalStore< f7: SignalStoreFeature, f8: SignalStoreFeature, f9: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -608,7 +614,7 @@ export function signalStore< f8: SignalStoreFeature, f9: SignalStoreFeature, f10: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -645,7 +651,7 @@ export function signalStore< f9: SignalStoreFeature, f10: SignalStoreFeature, f11: SignalStoreFeature -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -691,7 +697,7 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11, F12 > -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -743,7 +749,7 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12, F13 > -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -801,7 +807,7 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13, F14 > -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -865,12 +871,15 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13 & F14, F15 > -): Type & StateSource>>; +): Type & StateSource>>>; export function signalStore( config: { providedIn?: 'root'; protectedState: false }, f1: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & + WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -879,7 +888,9 @@ export function signalStore< config: { providedIn?: 'root'; protectedState: false }, f1: SignalStoreFeature, f2: SignalStoreFeature<{} & F1, F2> -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -890,7 +901,9 @@ export function signalStore< f1: SignalStoreFeature, f2: SignalStoreFeature<{} & F1, F2>, f3: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -903,7 +916,9 @@ export function signalStore< f2: SignalStoreFeature<{} & F1, F2>, f3: SignalStoreFeature, f4: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -918,7 +933,9 @@ export function signalStore< f3: SignalStoreFeature, f4: SignalStoreFeature, f5: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -935,7 +952,9 @@ export function signalStore< f4: SignalStoreFeature, f5: SignalStoreFeature, f6: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -954,7 +973,9 @@ export function signalStore< f5: SignalStoreFeature, f6: SignalStoreFeature, f7: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -975,7 +996,9 @@ export function signalStore< f6: SignalStoreFeature, f7: SignalStoreFeature, f8: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -1006,7 +1029,9 @@ export function signalStore< f7: SignalStoreFeature, f8: SignalStoreFeature, f9: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -1040,7 +1065,9 @@ export function signalStore< f8: SignalStoreFeature, f9: SignalStoreFeature, f10: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -1077,7 +1104,9 @@ export function signalStore< f9: SignalStoreFeature, f10: SignalStoreFeature, f11: SignalStoreFeature -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -1123,7 +1152,9 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11, F12 > -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -1175,7 +1206,9 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12, F13 > -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -1233,7 +1266,9 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13, F14 > -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore< F1 extends SignalStoreFeatureResult, F2 extends SignalStoreFeatureResult, @@ -1297,7 +1332,9 @@ export function signalStore< F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13 & F14, F15 > -): Type & WritableStateSource>>; +): Type< + SignalStoreMembers & WritableStateSource>> +>; export function signalStore( ...args: [SignalStoreConfig, ...SignalStoreFeature[]] | SignalStoreFeature[] diff --git a/modules/signals/src/ts-helpers.ts b/modules/signals/src/ts-helpers.ts index a3a4a36cc6..f7df5cdb77 100644 --- a/modules/signals/src/ts-helpers.ts +++ b/modules/signals/src/ts-helpers.ts @@ -23,3 +23,7 @@ export type IsKnownRecord = IsRecord extends true ? false : true : false; + +export type OmitPrivate = { + [K in keyof T as K extends `_${string}` ? never : K]: T[K]; +}; diff --git a/projects/ngrx.io/content/guide/signals/faq.md b/projects/ngrx.io/content/guide/signals/faq.md index 1c3ea8a917..f4cd6df071 100644 --- a/projects/ngrx.io/content/guide/signals/faq.md +++ b/projects/ngrx.io/content/guide/signals/faq.md @@ -6,29 +6,6 @@ However, you could create a feature for this, or you can make use of the [`withDevtools` feature](https://github.com/angular-architects/ngrx-toolkit?tab=readme-ov-file#devtools-withdevtools) from the `@angular-architects/ngrx-toolkit` package. -
- Is there a way to define private methods on a SignalStore? - - Currently there's no built-in support for private properties. - To achieve this in the current version, you can resort to workarounds, e.g. by not returning them. - -```ts -withMethods(() => { - function privateFunction() { - /* implementation here */ - } - function publicFunction() { - /* implementation here */ - } - function publicFunction2() { - privateFunction(); - } - - return { publicFunction, publicFunction2 }; -}) -``` -
-
Can I interact with my NgRx Actions within a SignalStore? diff --git a/projects/ngrx.io/content/guide/signals/signal-store/entity-management.md b/projects/ngrx.io/content/guide/signals/signal-store/entity-management.md index 43e7fd3c75..7685a911bb 100644 --- a/projects/ngrx.io/content/guide/signals/signal-store/entity-management.md +++ b/projects/ngrx.io/content/guide/signals/signal-store/entity-management.md @@ -276,7 +276,7 @@ type Todo = { }; export const TodosStore = signalStore( - // 💡 entity type is specified using the `type` function + // 💡 Entity type is specified using the `type` function. withEntities({ entity: type<Todo>(), collection: 'todo' }), ); @@ -384,3 +384,41 @@ export const TodosStore = signalStore( ); + +## Private Entity Collections + +Private entity collections are defined by using the `_` prefix for the collection name. + +```ts +const todoConfig = entityConfig({ + entity: type(), + // 👇 private collection + collection: '_todo', +}); + +const TodosStore = signalStore( + withEntities(todoConfig), + withComputed(({ _todoEntities }) => ({ + // 👇 exposing entity array publicly + todos: _todoEntities, + })) +); + +@Component({ + /* ... */ + template: ` +

Todos

+ + `, + providers: [TodosStore], +}) +class TodosComponent { + readonly store = inject(TodosStore); +} +``` + +
+ +Learn more about private store members in the [Private Store Members](/guide/signals/signal-store/private-store-members) guide. + +
diff --git a/projects/ngrx.io/content/guide/signals/signal-store/private-store-members.md b/projects/ngrx.io/content/guide/signals/signal-store/private-store-members.md new file mode 100644 index 0000000000..6b65747f63 --- /dev/null +++ b/projects/ngrx.io/content/guide/signals/signal-store/private-store-members.md @@ -0,0 +1,67 @@ +# Private Store Members + +SignalStore allows defining private members that cannot be accessed from outside the store by using the `_` prefix. +This includes root-level state slices, computed signals, and methods. + + + + +import { computed } from '@angular/core'; +import { + patchState, + signalStore, + withComputed, + withMethods, + withState, +} from '@ngrx/signals'; + +export const CounterStore = signalStore( + withState({ + count1: 0, + // 👇 private state slice + _count2: 0, + }), + withComputed(({ count1, _count2 }) => ({ + // 👇 private computed signal + _doubleCount1: computed(() => count1() * 2), + doubleCount2: computed(() => _count2() * 2), + })), + withMethods((store) => ({ + increment1(): void { + patchState(store, { count1: store.count1() + 1 }); + }, + // 👇 private method + _increment2(): void { + patchState(store, { _count2: store._count2() + 1 }); + }, + })), +); + + + + + +import { Component, inject, OnInit } from '@angular/core'; +import { CounterStore } from './counter.store'; + +@Component({ + /* ... */ + providers: [CounterStore], +}) +export class CounterComponent implements OnInit { + readonly store = inject(CounterStore); + + ngOnInit(): void { + console.log(this.store.count1()); // ✅ + console.log(this.store._count2()); // ❌ + + console.log(this.store._doubleCount1()); // ❌ + console.log(this.store.doubleCount2()); // ✅ + + this.store.increment1(); // ✅ + this.store._increment2(); // ❌ + } +} + + + diff --git a/projects/ngrx.io/content/navigation.json b/projects/ngrx.io/content/navigation.json index b58c1fd66e..c71e3b7efe 100644 --- a/projects/ngrx.io/content/navigation.json +++ b/projects/ngrx.io/content/navigation.json @@ -297,6 +297,10 @@ "title": "State Tracking", "url": "guide/signals/signal-store/state-tracking" }, + { + "title": "Private Store Members", + "url": "guide/signals/signal-store/private-store-members" + }, { "title": "Custom Store Features", "url": "guide/signals/signal-store/custom-store-features"