scan() operator unexpectedly resets or fails to accumulate emissions across multiple emissions
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 31.7k
- Forks
- 3k
- PR merge metrics
- No merged PRs in 30d
Description
Describe the bug
When using the scan() operator in RxJS (version 7.8.1), the accumulator parameter (a) does not correctly retain previous values across emissions. Instead, it appears to be empty or reset with each new emission, even though the index parameter (i) increments as expected. This suggests that scan() is receiving consecutive emissions but failing to persist the accumulated state.
Expected behavior
- The scan() operator should correctly accumulate all previous emissions.
- The accumulator parameter (a) should contain the history of all prior emissions, growing over time.
Index: 1
Accumulator: []
Index: 2
Accumulator: [{ time: 1711010000000, view: [expandedTabs] }]
Index: 3
Accumulator: [{ time: 1711010000000, view: [expandedTabs] }, { time: 1711010500000, view: [] }]
Actual Behavior:
- The accumulator appears empty ([]) on each emission, despite the i index increasing.
- It behaves as though scan() is reinitializing or not retaining prior emissions properly.
Index: 1
Accumulator: []
Index: 2
Accumulator: []
Index: 3
Accumulator: []
Reproduction code
import { ReplaySubject, scan, map, tap, Observable } from 'rxjs';
enum DashboardViews {
expandedTabs = 'expandedTabs'
}
const views$ = new ReplaySubject<DashboardViews[]>(1);
const viewChange$: Observable<boolean> = views$.pipe(
map(view => ({ view, time: new Date().valueOf() })),
scan((a, c, i) => {
console.log('Index:', i); // Index increases correctly
console.log('Accumulator:', a); // Accumulator is unexpectedly empty on each emission
a.push(c);
return a;
}, [] as { time: number, view: DashboardViews[] }[]),
tap(v => console.log('Accumulated values:', v)),
map((views) => {
if (views.length < 2) return false;
const curr = views[views.length - 1];
const prev = views[views.length - 2];
return prev.view.includes(DashboardViews.expandedTabs) !== curr.view.includes(DashboardViews.expandedTabs);
})
);
// Simulate emissions
views$.next([DashboardViews.expandedTabs]);
views$.next([]);
views$.next([DashboardViews.expandedTabs]);
viewChange$.subscribe(v => console.log('viewChange', v));
Reproduction URL
No response
Version
7.8.1
Environment
- RxJS version: 7.81
- Angular version: 18.2.1
- Node.js version: 20.18.1
- Browser: Chrome 133
- OS: macOS 14.7.4 (23H420)
Additional context
- The issue persists even when using shareReplay(), defer(), and ReplaySubject() with a larger buffer size, suggesting that this may be a bug in the scan() operator itself.
- The i index increasing indicates that the emissions are consecutive, yet the accumulator does not persist previous values.
- This behavior does not align with the expected behavior of scan() in RxJS.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Begin at the scan() entry point and run the supplied TypeScript reproduction, checking the observable subscription and ReplaySubject setup before comparing accumulator behavior. Confirm the expected accumulation across emissions and use that result to determine whether the operator or reproduction needs attention.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100