Skip to main content

Focus Dropdown Bug Analysis

Problem Description

The "Add to Today's Focus or Tasks" dropdown button was working correctly in the Focus Tasks and Today's Tasks sections, but was completely non-functional in the Available Tasks section. When clicking the star button in the Available Tasks section, no dropdown would appear.

Root Cause Analysis

The Issue: Object Reference Changes

The problem was caused by how tasks are processed differently in each section:

  1. Focus Tasks & Today's Tasks: Use original Todo objects directly from the service
  2. Available Tasks: Use cloned/new Todo objects created by the filtering process

The Filtering Process

In the filteredAvailableTasks() method, the code performs complex filtering and creates new objects:

// This creates NEW Todo objects with different references
const taskWithFilteredSubtasks: Todo = {
...task,
subtasks: filteredSubtasks
};

Why This Breaks Change Detection

  1. Component State Storage: The dropdown state was stored as a component property (showFocusDropdown = false)
  2. Object Reference Changes: When filteredAvailableTasks() runs, it creates new Todo objects
  3. Component Recreation: Angular's change detection sees different object references and may recreate components
  4. State Loss: When components are recreated, the component-level state (showFocusDropdown) is lost

The Debugging Journey

We discovered this through systematic debugging:

  1. Console logs showed: Button clicks were detected and showFocusDropdown was being set to true
  2. DOM inspection revealed: No dropdown elements were being created (All dropdown elements found: 0)
  3. Template test confirmed: When we forced a dropdown to show with a static condition, it rendered correctly
  4. Change detection attempts failed: Even with manual markForCheck() and detectChanges(), the issue persisted

The Solution

Static State Management

Instead of storing dropdown state as component properties, we implemented a static Set that tracks open dropdowns by task ID:

// Before (broken)
showFocusDropdown = false;

// After (working)
private static openDropdowns = new Set<string>();

get showFocusDropdown(): boolean {
return TodoItemComponent.openDropdowns.has(this.todo.id);
}

Key Benefits

  1. Persistence: State survives component recreation
  2. ID-based tracking: Tied to task ID, not component instance
  3. Global management: One dropdown open at a time across all sections
  4. Consistent behavior: Works identically in all sections

Implementation Details

onFocusButtonClick(event: Event): void {
event.stopPropagation();

// Toggle dropdown state using static Set
if (TodoItemComponent.openDropdowns.has(this.todo.id)) {
TodoItemComponent.openDropdowns.delete(this.todo.id);
} else {
// Close all other dropdowns first
TodoItemComponent.openDropdowns.clear();
TodoItemComponent.openDropdowns.add(this.todo.id);
}

// Force change detection
this.cdr.markForCheck();
this.cdr.detectChanges();
}

Additional Fixes Applied

CSS Overflow Issues

We also fixed CSS issues that could clip dropdowns:

/* Before */
.available-section {
overflow: hidden; /* This was clipping dropdowns */
}

/* After */
.available-section {
overflow: visible; /* Allows dropdowns to appear outside container */
}

Lessons Learned

  1. Object Reference Stability: Be careful when creating new objects in computed properties or filters
  2. Component State vs Global State: Consider whether state should survive component recreation
  3. Change Detection Complexity: Angular's change detection can be affected by object reference changes
  4. Debugging Approach: Systematic debugging (console logs, DOM inspection, template tests) helped isolate the issue

Prevention Strategies

  1. Use trackBy functions in *ngFor to help Angular track object identity
  2. Consider using services for state that should persist across component lifecycles
  3. Be mindful of object cloning in computed properties and filters
  4. Test components in different rendering contexts to catch these issues early

Final Result

The dropdown now works consistently across all sections:

  • ✅ Focus Tasks section
  • ✅ Today's Tasks section
  • ✅ Available Tasks section

Users can now successfully add tasks to either "Today's Focus" or "Today's Tasks" from any section of the focus page.