Skip to content

Commit

Permalink
feat(signals): rename signals to computed when defining custom featur…
Browse files Browse the repository at this point in the history
…es with input (#4395)

Closes #4391
  • Loading branch information
markostanimirovic authored Jun 18, 2024
1 parent 6ae4723 commit 05f0940
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 99 deletions.
6 changes: 3 additions & 3 deletions modules/signals/entities/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export type NamedEntityState<Entity, Collection extends string> = {
[K in keyof EntityState<Entity> as `${Collection}${Capitalize<K>}`]: EntityState<Entity>[K];
};

export type EntitySignals<Entity> = {
export type EntityComputed<Entity> = {
entities: Signal<Entity[]>;
};

export type NamedEntitySignals<Entity, Collection extends string> = {
[K in keyof EntitySignals<Entity> as `${Collection}${Capitalize<K>}`]: EntitySignals<Entity>[K];
export type NamedEntityComputed<Entity, Collection extends string> = {
[K in keyof EntityComputed<Entity> as `${Collection}${Capitalize<K>}`]: EntityComputed<Entity>[K];
};

export type EntityIdProps<Entity> = {
Expand Down
16 changes: 8 additions & 8 deletions modules/signals/entities/src/with-entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,41 @@ import {
withState,
} from '@ngrx/signals';
import {
EntityComputed,
EntityId,
EntityMap,
EntitySignals,
EntityState,
NamedEntitySignals,
NamedEntityComputed,
NamedEntityState,
} from './models';
import { getEntityStateKeys } from './helpers';

export function withEntities<Entity>(): SignalStoreFeature<
{ state: {}; signals: {}; methods: {} },
{ state: {}; computed: {}; methods: {} },
{
state: EntityState<Entity>;
signals: EntitySignals<Entity>;
computed: EntityComputed<Entity>;
methods: {};
}
>;
export function withEntities<Entity, Collection extends string>(config: {
entity: Entity;
collection: Collection;
}): SignalStoreFeature<
{ state: {}; signals: {}; methods: {} },
{ state: {}; computed: {}; methods: {} },
{
state: NamedEntityState<Entity, Collection>;
signals: NamedEntitySignals<Entity, Collection>;
computed: NamedEntityComputed<Entity, Collection>;
methods: {};
}
>;
export function withEntities<Entity>(config: {
entity: Entity;
}): SignalStoreFeature<
{ state: {}; signals: {}; methods: {} },
{ state: {}; computed: {}; methods: {} },
{
state: EntityState<Entity>;
signals: EntitySignals<Entity>;
computed: EntityComputed<Entity>;
methods: {};
}
>;
Expand Down
2 changes: 1 addition & 1 deletion modules/signals/spec/signal-store-feature.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('signalStoreFeature', () => {
return signalStoreFeature(
{
state: type<{ foo: string }>(),
signals: type<{ s: Signal<number> }>(),
computed: type<{ s: Signal<number> }>(),
},
withState({ foo1: 1 }),
withState({ foo2: 2 })
Expand Down
10 changes: 5 additions & 5 deletions modules/signals/spec/types/signal-store.types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ describe('signalStore', () => {
return signalStoreFeature(
{
state: type<{ q1: string }>(),
signals: type<{ sig: Signal<boolean> }>(),
computed: type<{ sig: Signal<boolean> }>(),
},
withState({ y: initialY }),
withComputed(() => ({ sigY: computed(() => 'sigY') })),
Expand Down Expand Up @@ -739,7 +739,7 @@ describe('signalStore', () => {
);
const feature = signalStoreFeature(
{ signals: type<{ sig: Signal<boolean> }>() },
{ computed: type<{ sig: Signal<boolean> }>() },
withX(),
withState({ q1: 'q1' }),
withY(),
Expand Down Expand Up @@ -785,7 +785,7 @@ describe('signalStore', () => {
${baseSnippet}
const feature = signalStoreFeature(
{ signals: type<{ sig: Signal<boolean> }>() },
{ computed: type<{ sig: Signal<boolean> }>() },
withComputed(() => ({ sig: computed(() => 1) })),
withX(),
withState({ q1: 'q1' }),
Expand Down Expand Up @@ -817,7 +817,7 @@ describe('signalStore', () => {
);
const feature = signalStoreFeature(
{ signals: type<{ sig: Signal<boolean> }>() },
{ computed: type<{ sig: Signal<boolean> }>() },
withComputed(() => ({ sig: computed(() => 1) })),
withX(),
withState({ q1: 'q1' }),
Expand Down Expand Up @@ -860,7 +860,7 @@ describe('signalStore', () => {
entities: Entity[];
selectedEntity: Entity | null;
};
signals: {
computed: {
selectedEntity2: Signal<Entity | undefined>;
};
methods: {
Expand Down
24 changes: 15 additions & 9 deletions modules/signals/spec/with-computed.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import { withComputed, withMethods, withState } from '../src';
import { getInitialInnerStore } from '../src/signal-store';

describe('withComputed', () => {
it('adds signals to the store immutably', () => {
it('adds computed signals to the store immutably', () => {
const initialStore = getInitialInnerStore();

const s1 = signal('s1').asReadonly();
const s2 = signal(10).asReadonly();

const store = withComputed(() => ({ s1, s2 }))(initialStore);

expect(Object.keys(store.signals)).toEqual(['s1', 's2']);
expect(Object.keys(initialStore.signals)).toEqual([]);
expect(Object.keys(store.computedSignals)).toEqual(['s1', 's2']);
expect(Object.keys(initialStore.computedSignals)).toEqual([]);

expect(store.signals.s1).toBe(s1);
expect(store.signals.s2).toBe(s2);
expect(store.computedSignals.s1).toBe(s1);
expect(store.computedSignals.s2).toBe(s2);
});

it('overrides previously defined slices, signals, and methods with the same name', () => {
it('overrides previously defined state signals, computed signals, and methods with the same name', () => {
const initialStore = [
withState({
p1: 10,
Expand All @@ -42,10 +42,16 @@ describe('withComputed', () => {
s3: signal({ s: 3 }).asReadonly(),
}))(initialStore);

expect(Object.keys(store.signals)).toEqual(['s1', 's2', 'p1', 'm1', 's3']);
expect(store.signals.s2).toBe(s2);
expect(Object.keys(store.computedSignals)).toEqual([
's1',
's2',
'p1',
'm1',
's3',
]);
expect(store.computedSignals.s2).toBe(s2);

expect(Object.keys(store.slices)).toEqual(['p2']);
expect(Object.keys(store.stateSignals)).toEqual(['p2']);
expect(Object.keys(store.methods)).toEqual(['m2']);
});
});
6 changes: 3 additions & 3 deletions modules/signals/spec/with-methods.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('withMethods', () => {
expect(store.methods.m2).toBe(m2);
});

it('overrides previously defined slices, signals, and methods with the same name', () => {
it('overrides previously defined state signals, computed signals, and methods with the same name', () => {
const initialStore = [
withState({
p1: 'p1',
Expand All @@ -45,7 +45,7 @@ describe('withMethods', () => {
expect(Object.keys(store.methods)).toEqual(['m1', 'm2', 'p2', 's1', 'm3']);
expect(store.methods.m2).toBe(m2);

expect(Object.keys(store.slices)).toEqual(['p1']);
expect(Object.keys(store.signals)).toEqual(['s2']);
expect(Object.keys(store.stateSignals)).toEqual(['p1']);
expect(Object.keys(store.computedSignals)).toEqual(['s2']);
});
});
36 changes: 21 additions & 15 deletions modules/signals/spec/with-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ describe('withState', () => {
expect(state).toEqual({ foo: 'bar', x: { y: 'z' } });
expect(initialState).toEqual({});

expect(Object.keys(store.slices)).toEqual(['foo', 'x']);
expect(Object.keys(initialStore.slices)).toEqual([]);
expect(Object.keys(store.stateSignals)).toEqual(['foo', 'x']);
expect(Object.keys(initialStore.stateSignals)).toEqual([]);
});

it('creates deep signals for each state slice', () => {
Expand All @@ -29,14 +29,14 @@ describe('withState', () => {
x: { y: 'z' },
})(initialStore);

expect(store.slices.foo()).toBe('bar');
expect(isSignal(store.slices.foo)).toBe(true);
expect(store.stateSignals.foo()).toBe('bar');
expect(isSignal(store.stateSignals.foo)).toBe(true);

expect(store.slices.x()).toEqual({ y: 'z' });
expect(isSignal(store.slices.x)).toBe(true);
expect(store.stateSignals.x()).toEqual({ y: 'z' });
expect(isSignal(store.stateSignals.x)).toBe(true);

expect(store.slices.x.y()).toBe('z');
expect(isSignal(store.slices.x.y)).toBe(true);
expect(store.stateSignals.x.y()).toBe('z');
expect(isSignal(store.stateSignals.x.y)).toBe(true);
});

it('patches state signal and creates deep signals for state slices provided via factory', () => {
Expand All @@ -49,12 +49,12 @@ describe('withState', () => {
const state = store[STATE_SIGNAL]();

expect(state).toEqual({ foo: 'bar', x: { y: 'z' } });
expect(store.slices.foo()).toBe('bar');
expect(store.slices.x()).toEqual({ y: 'z' });
expect(store.slices.x.y()).toBe('z');
expect(store.stateSignals.foo()).toBe('bar');
expect(store.stateSignals.x()).toEqual({ y: 'z' });
expect(store.stateSignals.x.y()).toBe('z');
});

it('overrides previously defined state slices, signals, and methods with the same name', () => {
it('overrides previously defined state signals, computed signals, and methods with the same name', () => {
const initialStore = [
withState({
p1: 10,
Expand All @@ -77,10 +77,16 @@ describe('withState', () => {
p3: 'p3',
}))(initialStore);

expect(Object.keys(store.slices)).toEqual(['p1', 'p2', 's2', 'm2', 'p3']);
expect(store.slices.p2()).toBe(100);
expect(Object.keys(store.stateSignals)).toEqual([
'p1',
'p2',
's2',
'm2',
'p3',
]);
expect(store.stateSignals.p2()).toBe(100);

expect(Object.keys(store.signals)).toEqual(['s1']);
expect(Object.keys(store.computedSignals)).toEqual(['s1']);
expect(Object.keys(store.methods)).toEqual(['m1']);
});
});
26 changes: 12 additions & 14 deletions modules/signals/src/signal-store-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { IsKnownRecord, Prettify } from './ts-helpers';

export type SignalStoreConfig = { providedIn: 'root' };

export type SignalStoreSlices<State> = IsKnownRecord<
Prettify<State>
> extends true
export type StateSignals<State> = IsKnownRecord<Prettify<State>> extends true
? {
[Key in keyof State]: IsKnownRecord<State[Key]> extends true
? DeepSignal<State[Key]>
Expand All @@ -17,8 +15,8 @@ export type SignalStoreSlices<State> = IsKnownRecord<

export type SignalStoreProps<FeatureResult extends SignalStoreFeatureResult> =
Prettify<
SignalStoreSlices<FeatureResult['state']> &
FeatureResult['signals'] &
StateSignals<FeatureResult['state']> &
FeatureResult['computed'] &
FeatureResult['methods']
>;

Expand All @@ -33,29 +31,29 @@ export type SignalStoreHooks = {

export type InnerSignalStore<
State extends object = object,
Signals extends SignalsDictionary = SignalsDictionary,
ComputedSignals extends SignalsDictionary = SignalsDictionary,
Methods extends MethodsDictionary = MethodsDictionary
> = {
slices: SignalStoreSlices<State>;
signals: Signals;
stateSignals: StateSignals<State>;
computedSignals: ComputedSignals;
methods: Methods;
hooks: SignalStoreHooks;
} & StateSignal<State>;

export type SignalStoreFeatureResult = {
state: object;
signals: SignalsDictionary;
computed: SignalsDictionary;
methods: MethodsDictionary;
};

export type EmptyFeatureResult = { state: {}; signals: {}; methods: {} };
export type EmptyFeatureResult = { state: {}; computed: {}; methods: {} };

export type SignalStoreFeature<
Input extends SignalStoreFeatureResult = SignalStoreFeatureResult,
Output extends SignalStoreFeatureResult = SignalStoreFeatureResult
> = (
store: InnerSignalStore<Input['state'], Input['signals'], Input['methods']>
) => InnerSignalStore<Output['state'], Output['signals'], Output['methods']>;
store: InnerSignalStore<Input['state'], Input['computed'], Input['methods']>
) => InnerSignalStore<Output['state'], Output['computed'], Output['methods']>;

export type MergeFeatureResults<
FeatureResults extends SignalStoreFeatureResult[]
Expand All @@ -78,14 +76,14 @@ export type MergeFeatureResults<

type FeatureResultKeys<FeatureResult extends SignalStoreFeatureResult> =
| keyof FeatureResult['state']
| keyof FeatureResult['signals']
| keyof FeatureResult['computed']
| keyof FeatureResult['methods'];

type MergeTwoFeatureResults<
First extends SignalStoreFeatureResult,
Second extends SignalStoreFeatureResult
> = {
state: Omit<First['state'], FeatureResultKeys<Second>>;
signals: Omit<First['signals'], FeatureResultKeys<Second>>;
computed: Omit<First['computed'], FeatureResultKeys<Second>>;
methods: Omit<First['methods'], FeatureResultKeys<Second>>;
} & Second;
8 changes: 4 additions & 4 deletions modules/signals/src/signal-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,8 @@ export function signalStore(
(store, feature) => feature(store),
getInitialInnerStore()
);
const { slices, signals, methods, hooks } = innerStore;
const props = { ...slices, ...signals, ...methods };
const { stateSignals, computedSignals, methods, hooks } = innerStore;
const props = { ...stateSignals, ...computedSignals, ...methods };

(this as any)[STATE_SIGNAL] = innerStore[STATE_SIGNAL];

Expand All @@ -335,8 +335,8 @@ export function signalStore(
export function getInitialInnerStore(): InnerSignalStore {
return {
[STATE_SIGNAL]: signal({}),
slices: {},
signals: {},
stateSignals: {},
computedSignals: {},
methods: {},
hooks: {},
};
Expand Down
Loading

0 comments on commit 05f0940

Please sign in to comment.