The Challenge: Decoupling Display Visibility from Filter Availability

In analytical enterprise applications built with Java and Angular, comparing current calculation states against saved baselines—commonly called a Delta View—is a standard feature. A frequent structural pattern generates three distinct records for each row key:

  • The current calculation row
  • The saved baseline row (typically prefixed, e.g., *_)
  • The calculated variance row (e.g., prefixed with Δ_)

A classic bug emerges when generating this delta sheet using a union of keys (current ∪ saved). If a user saved an earlier calculation containing attributes like test1 and test2, but the current run only contains test3, both test1 and test2 are rendered into the table automatically. These obsolete (saved-only) attributes clutter the view.

However, simply deleting them from the delta sheet breaks the requirement that saved attributes must still be selectable in the attribute filter. Here is how to cleanly decouple the filter domain from default row visibility in Java 21 / Spring / Angular architectures.

Root Cause Analysis

The root cause lies in treating the combined delta sheet as both the data supplier for the filter dropdown and the immediate rendering source for table rows.

When merging keys via allRowKeys = deltaCalculationUtils.mergeRowKeys(baseKeys, savedKeys), populating the delta sheet with all combinations is technically correct. The failure happens downstream when applying default filter states: an uninitialized or "all checked" filter includes rows that have no representation in the active calculation run.

Architectural Solution

Rather than destroying data in buildDelta(), maintain two distinct sets in the user session or table state context:

  1. Obsolete Attributes (savedOnly): Attributes found exclusively in the saved baseline. Computed during delta generation.
  2. Explicitly Revealed Attributes (revealed): A subset of obsolete attributes that the user explicitly ticked in the filter dropdown.

1. Tagging Saved-Only Attributes in Delta Calculation

During the delta building process, identify attributes that exist solely in the baseline:

Set<String> currentAttributes = extractAttributes(baseSheet);\nSet<String> savedAttributes = extractAttributes(savedSheet);\n\n// Obsolete = saved attributes that are missing in the current calculation\nSet<String> obsoleteAttributes = new HashSet<>(savedAttributes);\nobsoleteAttributes.removeAll(currentAttributes);\n\n// Store in session or execution context\nsession.put(OBSOLETE_ATTRIBUTES, tableId, Collections.unmodifiableSet(obsoleteAttributes));\nsession.remove(REVEALED_ATTRIBUTES, tableId); // Reset when a new comparison starts\n

2. Updating Active Filters without Dropping Manual Selections

When the Angular frontend sends an updated ColumnFilter to the setActiveFilter endpoint, inspect whether the user deliberately included any obsolete values:

@POST\n@Path("/{tableId}/filter")\npublic Response setActiveFilter(@PathParam("tableId") UUID tableId, ColumnFilter filter) {\n    Set<String> obsolete = session.get(OBSOLETE_ATTRIBUTES, tableId);\n    if (obsolete == null) {\n        obsolete = Collections.emptySet();\n    }\n\n    Set<String> revealed = new HashSet<>();\n    \n    if (!filter.isAllChecked() && filter.getInverseValues() != null) {\n        // inverseValues contains explicitly SELECTED items\n        for (String val : filter.getInverseValues()) {\n            if (obsolete.contains(val)) {\n                revealed.add(val);\n            }\n        }\n    } else if (filter.isAllChecked() && filter.getInverseValues() != null) {\n        // inverseValues contains EXCLUDED items\n        Set<String> excluded = new HashSet<>(filter.getInverseValues());\n        for (String obs : obsolete) {\n            if (!excluded.contains(obs)) {\n                revealed.add(obs);\n            }\n        }\n    }\n\n    session.put(REVEALED_ATTRIBUTES, tableId, revealed);\n    applyTableFilters(tableId);\n    return Response.ok().build();\n}\n

3. Filtering Table Rows Prior to Serialization

When serializing rows for display in Delta Mode, filter out obsolete rows unless they have been explicitly added to the revealed collection:

public List<Row> getDisplayRows(UUID tableId, ViewMode viewMode) {\n    List<Row> rows = session.get(DELTA_RESULTS, tableId);\n    \n    if (viewMode != ViewMode.DELTA || rows == null) {\n        return rows;\n    }\n\n    Set<String> obsolete = session.getOrDefault(OBSOLETE_ATTRIBUTES, tableId, Collections.emptySet());\n    Set<String> revealed = session.getOrDefault(REVEALED_ATTRIBUTES, tableId, Collections.emptySet());\n\n    // Hidden attributes = obsolete attributes that have NOT been revealed\n    Set<String> hiddenAttributes = new HashSet<>(obsolete);\n    hiddenAttributes.removeAll(revealed);\n\n    if (hiddenAttributes.isEmpty()) {\n        return rows;\n    }\n\n    return rows.stream()\n        .filter(row -> !hiddenAttributes.contains(stripPrefix(row.getAttributeValue())))\n        .collect(Collectors.toList());\n}\n\nprivate String stripPrefix(String value) {\n    if (value == null) return "";\n    if (value.startsWith("*_")) return value.substring(2);\n    if (value.startsWith("Δ_")) return value.substring(2);\n    return value;\n}\n

Lifecycle Rules to Enforce

To satisfy edge-case requirements across recalculation, resets, and toggling, observe these lifecycle rules:

  • New Delta Calculation or Toggling Mode: Clear the REVEALED_ATTRIBUTES state. Recalculate OBSOLETE_ATTRIBUTES. Obsolete entries revert to being hidden by default.
  • In-place Recalculation: Keep REVEALED_ATTRIBUTES intact. If the user explicitly checked test1 and clicks "Recalculate" without exiting Delta mode, test1 remains visible.
  • Reset to Locked State: Rebuild the delta matrix, but preserve the user filter mapping so manual selections persist.
  • Standard Mode (Non-Delta): Bypass the hiddenAttributes check entirely, returning only baseSheet rows.

Conclusion

By separating the domain universe (all possible keys across current and saved states for filter dropdowns) from the default display criteria (visible table rows), you achieve intuitive behavior without compromising session consistency or breaking existing REST API filter contracts.