Hone logo
Hone
Problems

Implementing Optimistic Updates in an Angular To-Do List

This challenge focuses on implementing optimistic updates in an Angular application. Optimistic updates are a UI pattern where the user interface is updated immediately upon user action, before the server has confirmed the action. This provides a much smoother and more responsive user experience, especially for actions that might take a short while to complete.

Problem Description

You are tasked with building a to-do list feature in an Angular application. This feature should allow users to mark to-do items as complete and uncomplete. To enhance the user experience, you need to implement optimistic updates for the completion status toggle.

What needs to be achieved:

  • Display a list of to-do items, each with a title and a checkbox to indicate its completion status.
  • When a user clicks the checkbox to mark a to-do item as complete or uncomplete, the UI should immediately reflect this change.
  • The actual API call to persist the change should happen in the background.
  • If the API call fails, the UI should revert to its previous state, providing feedback to the user about the error.

Key requirements:

  1. UI Display: Render a list of to-do items. Each item should show its title and a checkbox. Checked checkboxes indicate completed items.
  2. Optimistic Update: When a user clicks a checkbox:
    • Immediately update the visual state of the checkbox (checked/unchecked) in the UI.
    • Trigger an asynchronous API call to update the item's completion status on the server.
  3. API Simulation: You will be provided with a simulated API service. This service will have methods for fetching to-do items and updating an item's status. The update method should sometimes simulate a network error (e.g., randomly fail).
  4. Error Handling and Rollback: If the API call for updating an item's status fails, the UI for that specific item must revert to its state before the optimistic update. Inform the user about the failure (e.g., with a toast message or by temporarily showing an error indicator).
  5. Data Management: Ensure that your application's state correctly reflects the actual server state after successful updates or rollbacks.

Expected behavior:

  • User sees a list of to-dos.
  • User clicks a checkbox for a to-do.
  • The checkbox immediately toggles.
  • A background process attempts to update the server.
  • If the server update succeeds, the UI remains as is.
  • If the server update fails, the checkbox for that specific to-do reverts to its original state, and an error is displayed to the user.

Important edge cases to consider:

  • Multiple users clicking checkboxes rapidly for the same or different items.
  • Network interruptions that cause API calls to fail.
  • Initial loading of to-do items.

Examples

Example 1: Successful Update

Input (Initial State): A list of to-do items fetched from the API.

[
  { "id": 1, "title": "Buy groceries", "completed": false },
  { "id": 2, "title": "Walk the dog", "completed": false }
]

User Action: User clicks the checkbox for "Buy groceries".

UI Update (Optimistic): The checkbox for "Buy groceries" immediately becomes checked.

[
  { "id": 1, "title": "Buy groceries", "completed": true }, // UI updated immediately
  { "id": 2, "title": "Walk the dog", "completed": false }
]

API Call: todoService.update(1, { completed: true }) is made.

API Response (Success): The API call completes successfully.

Final UI State: Remains as the optimistically updated state.

Explanation: The UI was updated instantly, providing immediate feedback to the user. The API call then confirmed this change.

Example 2: Failed Update

Input (Initial State): A list of to-do items fetched from the API.

[
  { "id": 1, "title": "Buy groceries", "completed": false },
  { "id": 2, "title": "Walk the dog", "completed": false }
]

User Action: User clicks the checkbox for "Buy groceries".

UI Update (Optimistic): The checkbox for "Buy groceries" immediately becomes checked.

[
  { "id": 1, "title": "Buy groceries", "completed": true }, // UI updated immediately
  { "id": 2, "title": "Walk the dog", "completed": false }
]

API Call: todoService.update(1, { completed: true }) is made.

API Response (Failure): The API call fails (e.g., due to a network error or server-side issue).

UI Rollback: The checkbox for "Buy groceries" reverts to its unchecked state. An error message is displayed to the user.

[
  { "id": 1, "title": "Buy groceries", "completed": false }, // UI rolled back
  { "id": 2, "title": "Walk the dog", "completed": false }
]

Explanation: The UI was updated optimistically, but the server confirmation failed. The system then rolled back the UI to the previous state and notified the user of the issue.

Constraints

  • The simulated TodoService will have the following methods:
    • getTodos(): Observable<Todo[]> - Fetches the list of to-do items.
    • updateTodo(id: number, changes: Partial<Todo>): Observable<Todo> - Updates a to-do item. This method should randomly fail approximately 20% of the time to simulate network errors.
  • You will be provided with a Todo interface:
    interface Todo {
      id: number;
      title: string;
      completed: boolean;
    }
    
  • Your Angular application should be structured using components and services.
  • Use RxJS operators effectively for managing asynchronous operations and state.

Notes

  • Consider how you will manage the state of your to-do items. Should you maintain a local copy that is updated optimistically, or directly modify the source of truth?
  • Think about how to clearly indicate to the user that an operation is in progress and what happens if it fails.
  • The provided TodoService will be a mock that simulates API behavior. You will not need to implement actual HTTP requests.
  • Focus on the logic of optimistic updates and error handling. A basic UI for displaying the list and checkboxes is sufficient.
  • You might want to consider using a state management library if the application grows in complexity, but for this challenge, simple service-based state management will suffice.
  • A good approach involves storing the original state of an item before making the optimistic update, so you can revert if necessary.
Loading editor...
typescript