Skip to content

Commit

Permalink
feat(signals): update to NgRx v18 (#33)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

This version requires you to update to NgRx (Signals) v18 to be compatible.
  • Loading branch information
timdeschryver authored Jul 26, 2024
1 parent 4570e58 commit 46a5280
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 21 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@angular/compiler-cli": "^18.1.0",
"@angular/core": "^18.1.0",
"@ngrx/component-store": "^18.0.0",
"@ngrx/signals": "18.0.0-rc.2",
"@ngrx/signals": "^18.0.0",
"@ngrx/store": "^18.0.0",
"@types/jest": "^29.5.12",
"cpy-cli": "^5.0.0",
Expand Down
6 changes: 3 additions & 3 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
},
"peerDependencies": {
"immer": ">= 7.0.0",
"@ngrx/component-store": ">= 13.0.0",
"@ngrx/store": ">= 13.0.0",
"@ngrx/signals": ">= 17.1.1 < 18.0.0"
"@ngrx/component-store": ">= 18.0.0",
"@ngrx/store": ">= 18.0.0",
"@ngrx/signals": ">= 18.0.0"
},
"peerDependenciesMeta": {
"@ngrx/component-store": {
Expand Down
6 changes: 2 additions & 4 deletions src/signals/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PartialStateUpdater, patchState, StateSignal } from '@ngrx/signals';
import { PartialStateUpdater, patchState, WritableStateSource, Prettify } from '@ngrx/signals';
import { immerReducer } from 'ngrx-immer';

export type ImmerStateUpdater<State extends object> = (state: State) => void;
Expand All @@ -12,9 +12,7 @@ function toFullStateUpdater<State extends object>(updater: PartialStateUpdater<S
return;
};
}

type Prettify<T> = { [K in keyof T]: T[K] } & {};
export function immerPatchState<State extends object>(state: StateSignal<State>, ...updaters: Array<Partial<Prettify<State>> | PartialStateUpdater<Prettify<State>> | ImmerStateUpdater<Prettify<State>>>) {
export function immerPatchState<State extends object>(state: WritableStateSource<State>, ...updaters: Array<Partial<Prettify<State>> | PartialStateUpdater<Prettify<State>> | ImmerStateUpdater<Prettify<State>>>): void {
const immerUpdaters = updaters.map(updater => {
if (typeof updater === 'function') {
return immerReducer(toFullStateUpdater(updater)) as unknown as PartialStateUpdater<State & {}>;
Expand Down
76 changes: 63 additions & 13 deletions src/signals/tests/immer-patch-state.jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,28 @@ import {
PartialStateUpdater,
signalStore,
withComputed,
withMethods,
withState,
} from '@ngrx/signals';
import { immerPatchState } from 'ngrx-immer/signals';
import { computed, effect } from '@angular/core';
import { TestBed } from '@angular/core/testing';

const UserState = signalStore(
withState({
id: 1,
name: { firstname: 'Konrad', lastname: 'Schultz' },
address: { city: 'Vienna', zip: '1010' },
}),
withComputed(({ name }) => ({
prettyName: computed(() => `${name.firstname()} ${name.lastname()}`),
})),
);

describe('immerPatchState', () => {
describe('immerPatchState (unprotected)', () => {
const UnprotectedUserState = signalStore(
{ protectedState: false },
withState({
id: 1,
name: { firstname: 'Konrad', lastname: 'Schultz' },
address: { city: 'Vienna', zip: '1010' },
}),
withComputed(({ name }) => ({
prettyName: computed(() => `${name.firstname()} ${name.lastname()}`),
})),
);

const setup = () => {
return new UserState();
return new UnprotectedUserState();
};

it('smoketest', () => {
Expand Down Expand Up @@ -192,3 +194,51 @@ describe('immerPatchState', () => {
});
});
});

describe('immerPatchState (protected)', () => {
const ProtectedUserState = signalStore(
{ protectedState: true },
withState({
id: 1,
name: { firstname: 'Konrad', lastname: 'Schultz' },
address: { city: 'Vienna', zip: '1010' },
}),
withComputed(({ name }) => ({
prettyName: computed(() => `${name.firstname()} ${name.lastname()}`),
})),
withMethods((store) => ({
setName: (name: {firstname:string, lastname:string}) => immerPatchState(store, { name }),
incrementId: () => immerPatchState(store, state => {
state.id++;
}),
}))
);

const setup = () => {
return new ProtectedUserState();
};

it('smoketest', () => {
const userState = setup();
expect(userState.id()).toBe(1);
});

it('state is protected and cannot be updated from the outside', () => {
const userState = setup();

expect(() => {
// @ts-ignore
immerPatchState(userState, (state) => ({ number: 1 }));
}).toThrow();
});

it('allows patching protected state using withMethods', () => {
const userState = setup();

userState.incrementId();
userState.setName({ firstname: 'Lucy', lastname: 'Sanders' });

expect(userState.prettyName()).toBe('Lucy Sanders');
expect(userState.id()).toBe(2);
});
});

0 comments on commit 46a5280

Please sign in to comment.