Skip to content

Commit e6fffec

Browse files
committed
fix!: allow required signals to be read in a selector
1 parent 7d5226a commit e6fffec

File tree

3 files changed

+46
-4
lines changed

3 files changed

+46
-4
lines changed

projects/angular-redux/src/lib/inject-selector.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DestroyRef,
55
effect,
66
inject,
7+
linkedSignal,
78
Signal,
89
signal,
910
} from '@angular/core';
@@ -90,7 +91,7 @@ export function createSelectorInjection(): InjectSelector {
9091

9192
const { store, subscription } = reduxContext;
9293

93-
const selectedState = signal(selector(store.getState()));
94+
const selectedState = linkedSignal(() => selector(store.getState()));
9495

9596
const unsubscribe = subscription.addNestedSub(() => {
9697
const data = selector(store.getState());
@@ -105,7 +106,7 @@ export function createSelectorInjection(): InjectSelector {
105106
unsubscribe();
106107
});
107108

108-
return selectedState;
109+
return selectedState.asReadonly();
109110
};
110111

111112
Object.assign(injectSelector, {

projects/angular-redux/src/tests/inject-selector-and-dispatch.spec.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from '@angular/core';
1+
import { Component, input } from '@angular/core';
22
import { render, waitFor } from '@testing-library/angular';
33
import '@testing-library/jest-dom';
44
import { configureStore, createSlice } from '@reduxjs/toolkit';
@@ -100,3 +100,44 @@ it('should show a value dispatched during ngOnInit', async () => {
100100

101101
await waitFor(() => expect(getByText('Count: 1')).toBeInTheDocument());
102102
});
103+
104+
it("should not throw an error on a required input passed to the selector's fn", async () => {
105+
const store = configureStore({
106+
reducer: {
107+
counter: counterSlice.reducer,
108+
},
109+
});
110+
111+
@Component({
112+
selector: 'app-count-and-add',
113+
standalone: true,
114+
template: `
115+
<button aria-label="Increment value" (click)="dispatch(increment())">
116+
Increment
117+
</button>
118+
<p>Count: {{ count() }}</p>
119+
`,
120+
})
121+
class CountAndAdd {
122+
dispatch = injectDispatch();
123+
increment = counterSlice.actions.increment;
124+
addBy = input.required<number>();
125+
count = injectSelector((state: any) => state.counter.value + this.addBy());
126+
}
127+
128+
@Component({
129+
selector: 'app-root',
130+
imports: [CountAndAdd],
131+
standalone: true,
132+
template: '<app-count-and-add [addBy]="12"/>',
133+
})
134+
class App {}
135+
136+
const { getByText, getByLabelText } = await render(App, {
137+
providers: [provideRedux({ store })],
138+
});
139+
140+
await waitFor(() => expect(getByText('Count: 12')).toBeInTheDocument());
141+
await user.click(getByLabelText('Increment value'));
142+
await waitFor(() => expect(getByText('Count: 13')).toBeInTheDocument());
143+
});

projects/angular-redux/src/tests/inject-selector.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,6 @@ describe('performance optimizations and bail-outs', () => {
319319
store.dispatch({ type: '' });
320320

321321
await waitFor(() => expect(selector).toHaveBeenCalledTimes(2));
322-
expect(renderedItems.length).toEqual(2);
322+
expect(renderedItems.length).toEqual(1);
323323
});
324324
});

0 commit comments

Comments
 (0)