Implement Time-Travel Debugging in an Angular Application
This challenge focuses on building a core component of "time-travel debugging" within an Angular application. Time-travel debugging allows developers to step back through a series of application states, observe how data and UI have changed over time, and identify bugs by pinpointing the exact moment an issue occurred. You will implement a service that records state changes and provides mechanisms to navigate through these historical states.
Problem Description
Your task is to create an Angular service that can track the state of a specific observable and allow users to "rewind" and "fast-forward" through past states of that observable. This service will act as a simplified time-travel debugger for a particular piece of application data.
Key Requirements:
- State Recording: The service must record every new value emitted by a target observable.
- History Storage: Store a limited history of emitted values. The number of states to store should be configurable.
- Current State Access: Provide a way to access the current state being displayed.
- Rewind Functionality: Implement a method to move the displayed state to a previous recorded state.
- Fast-Forward Functionality: Implement a method to move the displayed state to a future recorded state (after rewinding).
- Replayability: The service should emit the currently selected state, allowing the UI to react to historical data.
- Configuration: Allow configuration of the maximum number of states to store in the history.
Expected Behavior:
When an observable connected to the service emits a new value, it should be added to the history. The "current" position in the history should advance.
- Calling
rewind()should move the current position one step back in the history, emitting the state at that new position. - Calling
fastForward()should move the current position one step forward in the history, emitting the state at that new position. - If
rewind()is called when at the earliest state, no change should occur. - If
fastForward()is called when at the latest state, no change should occur. - If the history is full, the oldest state should be discarded when a new state is added.
Edge Cases:
- Initial state: What happens when the service is initialized before any values are emitted?
- Empty history: What happens when
rewind()orfastForward()are called on an empty history? - History limit reached: Ensure older states are correctly discarded.
- Concurrent updates: While this challenge doesn't explicitly focus on complex concurrency, assume single-threaded updates for simplicity.
Examples
Let's assume we have a simple counter observable that emits values 0, 1, 2, 3, 4.
Example 1: Basic Recording and Rewind
-
Input to Service Setup:
maxHistory: 5 -
Observable Emissions:
0, 1, 2, 3, 4 -
Service State After Emissions:
- History:
[0, 1, 2, 3, 4] - Current Index:
4(pointing to4) - Output Observable emits:
4
- History:
-
Action: Call
rewind()once. -
Output Observable emits:
3 -
Explanation: The current index moves from
4to3, and the service emits the value at index3, which is3. -
Action: Call
rewind()again. -
Output Observable emits:
2 -
Explanation: The current index moves from
3to2, and the service emits the value at index2, which is2.
Example 2: Fast-Forwarding After Rewind
-
Scenario: Starting from the state in Example 1, after rewinding twice (current index is
2, output is2). -
Action: Call
fastForward()once. -
Output Observable emits:
3 -
Explanation: The current index moves from
2to3, and the service emits the value at index3, which is3. -
Action: Call
fastForward()again. -
Output Observable emits:
4 -
Explanation: The current index moves from
3to4, and the service emits the value at index4, which is4. -
Action: Call
fastForward()again. -
Output Observable emits:
4(no change) -
Explanation: The current index is already at the latest state (
4), sofastForward()has no effect.
Example 3: History Limit
-
Input to Service Setup:
maxHistory: 3 -
Observable Emissions:
A, B, C, D, E -
Service State After Emissions:
- History:
[C, D, E](A and B have been discarded) - Current Index:
2(pointing toE) - Output Observable emits:
E
- History:
-
Action: Call
rewind()once. -
Output Observable emits:
D -
Explanation: The current index moves from
2to1, and the service emits the value at index1, which isD.
Constraints
- The
maxHistoryconfiguration parameter will be an integer between1and100. - The observable being tracked can emit values of any type (e.g.,
number,string,object). - The solution should be implemented as an Angular service using TypeScript.
- The service should be efficient enough to handle frequent state updates without significant performance degradation.
Notes
- Consider using RxJS operators to manage the observable streams.
- Think about how to represent the "current" position within the history array.
- The service should ideally expose an observable that emits the currently active state.
- This challenge is a simplified version of state management found in libraries like Redux with
redux-undoor Zustand's time travel middleware. - Focus on the core logic of state recording and navigation. UI rendering is outside the scope of this challenge.