Skip to content

Commit d003b85

Browse files
fix(store): infer initial store state properly with metareducers (#3102)
Closes #3007 BREAKING CHANGES: `initialState` needs to match the interface of the store/feature. BEFORE: Missing properties were valid ```ts StoreModule.forRoot(reducers, { initialState: { notExisting: 3 }, metaReducers: [metaReducer] }); ``` AFTER: A type error is produced for initialState that does not match the store/feature ```ts StoreModule.forRoot(reducers, { initialState: { notExisting: 3 }, metaReducers: [metaReducer] }); ```
1 parent 548c72c commit d003b85

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { expecter } from 'ts-snippet';
2+
import { compilerOptions } from './utils';
3+
4+
describe('StoreModule', () => {
5+
const expectSnippet = expecter(
6+
(code) => `
7+
import {StoreModule,ActionReducerMap,Action} from '@ngrx/store';
8+
9+
interface State {
10+
featureA: object;
11+
featureB: object;
12+
}
13+
14+
const reducers: ActionReducerMap<State, Action> = {} as ActionReducerMap<
15+
State,
16+
Action
17+
>;
18+
19+
function metaReducer(reducer) {
20+
return (state, action) => {
21+
return reducer(state, action);
22+
};
23+
}
24+
25+
${code}
26+
`,
27+
compilerOptions()
28+
);
29+
30+
describe('StoreModule.forRoot()', () => {
31+
it('accepts initialState and metaReducers with matching State', () => {
32+
expectSnippet(`
33+
StoreModule.forRoot(reducers, {
34+
initialState: { featureA: {} },
35+
metaReducers: [metaReducer]
36+
});
37+
`).toSucceed();
38+
});
39+
40+
it("throws when initial state don't with store state", () => {
41+
expectSnippet(`
42+
StoreModule.forRoot(reducers, {
43+
initialState: { notExisting: 3 },
44+
metaReducers: [metaReducer]
45+
});
46+
`).toFail(
47+
/Type '{ notExisting: number; }' is not assignable to type 'InitialState<State>/
48+
);
49+
});
50+
});
51+
52+
describe('StoreModule.forFeature()', () => {
53+
it('accepts initialState and metaReducers with matching State', () => {
54+
expectSnippet(`
55+
StoreModule.forFeature('feature', reducers, {
56+
initialState: { featureA: {} },
57+
metaReducers: [metaReducer]
58+
});
59+
`).toSucceed();
60+
});
61+
62+
it("throws when initial state don't with store state", () => {
63+
expectSnippet(`
64+
StoreModule.forFeature('feature', reducers, {
65+
initialState: { notExisting: 3 },
66+
metaReducers: [metaReducer]
67+
});
68+
`).toFail(
69+
/Type '{ notExisting: number; }' is not assignable to type 'InitialState<State>/
70+
);
71+
});
72+
});
73+
});

modules/store/src/store_module.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import {
3737
USER_PROVIDED_META_REDUCERS,
3838
_RESOLVED_META_REDUCERS,
3939
_ROOT_STORE_GUARD,
40-
ACTIVE_RUNTIME_CHECKS,
4140
_ACTION_TYPE_UNIQUENESS_CHECK,
4241
} from './tokens';
4342
import { ACTIONS_SUBJECT_PROVIDERS, ActionsSubject } from './actions_subject';
@@ -108,7 +107,7 @@ export class StoreFeatureModule implements OnDestroy {
108107
export interface StoreConfig<T, V extends Action = Action> {
109108
initialState?: InitialState<T>;
110109
reducerFactory?: ActionReducerFactory<T, V>;
111-
metaReducers?: MetaReducer<T, V>[];
110+
metaReducers?: MetaReducer<{ [P in keyof T]: T[P] }, V>[];
112111
}
113112

114113
export interface RootStoreConfig<T, V extends Action = Action>

0 commit comments

Comments
 (0)