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:
- Focus Tasks & Today's Tasks: Use original
Todoobjects directly from the service - Available Tasks: Use cloned/new
Todoobjects 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
- Component State Storage: The dropdown state was stored as a component property (
showFocusDropdown = false) - Object Reference Changes: When
filteredAvailableTasks()runs, it creates newTodoobjects - Component Recreation: Angular's change detection sees different object references and may recreate components
- State Loss: When components are recreated, the component-level state (
showFocusDropdown) is lost
The Debugging Journey
We discovered this through systematic debugging:
- Console logs showed: Button clicks were detected and
showFocusDropdownwas being set totrue - DOM inspection revealed: No dropdown elements were being created (
All dropdown elements found: 0) - Template test confirmed: When we forced a dropdown to show with a static condition, it rendered correctly
- Change detection attempts failed: Even with manual
markForCheck()anddetectChanges(), 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
- Persistence: State survives component recreation
- ID-based tracking: Tied to task ID, not component instance
- Global management: One dropdown open at a time across all sections
- 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
- Object Reference Stability: Be careful when creating new objects in computed properties or filters
- Component State vs Global State: Consider whether state should survive component recreation
- Change Detection Complexity: Angular's change detection can be affected by object reference changes
- Debugging Approach: Systematic debugging (console logs, DOM inspection, template tests) helped isolate the issue
Prevention Strategies
- Use trackBy functions in
*ngForto help Angular track object identity - Consider using services for state that should persist across component lifecycles
- Be mindful of object cloning in computed properties and filters
- 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.