2026-03-10
Iterative Table Formula Integration — Column Sums Only
Achievements
- Iterative Table Support: Extended the formula builder to discover and expose column sum aggregate cells from iterative (dynamic-row) tables. Previously, iterative tables were completely excluded from formula suggestions.
- Configured Aggregate Labels: Formula suggestion labels for row sum and column sum cells now use the actual configured
rowAggregateLabelandcolumnAggregateLabelfrom the table (defaulting to'Sum'), instead of hardcoded strings like'Total'or'Sum'.
Technical Decisions
- Why only column sums for iterative rows: Iterative tables let users add and remove rows at runtime. Row keys are sequential numeric indices (
0,1,2...) that re-index when any row is deleted. For example, if a user has rows0, 1, 2and deletes row1, row2silently becomes row1. If a formula referencedmatrix_abc_2_col_1(Row 3, Column 1), after that deletion the keymatrix_abc_2_col_1no longer exists — the data that was in Row 3 is now atmatrix_abc_1_col_1. The formula would either fail silently or compute against wrong data. This makes individual cell keys and row sum keys (matrixKey_N_sum) inherently unstable and unsafe to reference in formulas. - Why row aggregates are excluded too: Row aggregate keys follow the same pattern as cell keys —
matrixKey_<rowIndex>_sum. The row index (0,1,2...) is a runtime value that shifts when rows are deleted. Additionally, at design time (when the formula builder runs),rows: []is empty so there is literally nothing to generate suggestions from. Column aggregates work because their keys (matrixKey_colKey_sum) derive from the column config (a structural property), not from runtime row data. Put simply: column aggregates are a property of the table's structure (columns), while row aggregates are a property of the table's data (rows). For iterative tables, data is runtime-only, so only structural keys survive. - Column sums are stable: Column sum keys (
matrixKey_colKey_sum,matrixKey_groupKey_colKey_sum) are derived from the table's column configuration, not from runtime row state. They exist regardless of how many rows the user adds or removes, making them the only safe iterative table keys to expose in formulas. - No new files or services needed: The change was a surgical modification to the existing
updateFormulaFieldSuggestionsmethod inright-panel-static.component.ts, replacing the blanketif (el.iterativeTable) return;early exit with targeted column sum extraction.
Matrix Formula Guards — Prevent Destructive Configuration Changes
Achievements
-
Centralized formula reference checking in
DynaformService:- Updated the regex in
isFieldReferencedInFormulasto use\bword boundaries for accurate exact matching (matrix keys liketable_row_1_col_1need full-key matching, not partial). - Added
areAnyKeysReferenced(keys: string[])— a multi-key, short-circuit checker that iterates all keys and returns references from the first matched key.
- Updated the regex in
-
Implemented guards in
RightPanelStaticComponent:onColumnTypeChange(item, col, newType, groupKey?): Prevents changing a column's type fromnumberto anything else if any of its cells are currently referenced in a formula. Reverts thengModelvalue viasetTimeout.onToggleRowAggregate(item, newValue): Prevents disabling row aggregates if any row-sum cells (matrixKey_rowKey_sum) are referenced. Reverts the toggle on block.onToggleColumnAggregate(item, newValue): Same guard for column aggregates.onToggleIterativeTable(item): Prevents switching to iterative mode if any static row cells or row aggregates are referenced in formulas (those keys would become invalid/unstable in iterative mode).removeMatrixRow,removeSimpleMatrixColumn,removeMatrixColumn,removeMatrixColumnGroup: All deletion methods now guard against deleting a row/column/group if any of the cells it contains are referenced in formulas.
-
Added
getFormulaKeysForMatrixStructure(item, options)helper — generates the exact set of formula jsonKeys for any structural context (a specific row, a simple column, a grouped column, an entire group, or all aggregates), handling bothsimpleColumnanduseColumnGroupsmatrix layouts. -
Wired HTML template bindings: Changed
[(ngModel)]to[ngModel]+(ngModelChange)="handler()"for column type dropdowns and aggregate toggle switches, so new guard methods are invoked correctly on each change.
Problems Encountered & Decisions
- Wrong matrix prefix field (
item.matrixIdvsitem.matrixKey):- The guard's key-generation helper
getFormulaKeysForMatrixStructurewas computing the matrix prefix asitem.matrixId || item.jsonKey || ''. - However, all formula cell values are stored using
item.matrixKey(e.g., the cell keytable_row_1_col_1uses"table"which isitem.matrixKey, notitem.matrixId). - The guard was generating keys like
someJsonKey_row_1_col_1while the formulas storedtable_row_1_col_1— these never matched, soareAnyKeysReferencedalways returned[]. - Confirmed by cross-referencing with
updateFormulaFieldSuggestions(line 1678) and the group/simple mode migration helpers (lines 1139, 1167) — all useitem.matrixKey || item.jsonKey || ''. - Fix: Changed the one line in
getFormulaKeysForMatrixStructuretoconst matrixKey = item.matrixKey || item.jsonKey || ''. - Debug approach used: Added temporary
console.loginisFieldReferencedInFormulas(logging the regex and each formula being tested) and inonColumnTypeChange(logging the generated keys). User confirmed the keys looked correct (table_row_1_col_1) but references returned[]. Cross-referencingupdateFormulaFieldSuggestionsrevealed the field name mismatch.
- The guard's key-generation helper
Files Modified
src/ifile-teapot-web-dynaforms/services/dynaform.service.ts— AddedareAnyKeysReferenced.src/ifile-teapot-web-dynaforms/components/dynaform-builder/right-panel-static/right-panel-static.component.ts— AddedonColumnTypeChange,onToggleRowAggregate,onToggleColumnAggregate,getFormulaKeysForMatrixStructureand guards in all deletion methods +onToggleIterativeTable.src/ifile-teapot-web-dynaforms/components/dynaform-builder/right-panel-static/right-panel-static.component.html— Updated column type dropdowns and aggregate toggle bindings to use[ngModel]+(ngModelChange).
Refactoring Guards: ChangeDetectorRef vs setTimeout
Achievements
- Removed
setTimeoutfrom Matrix Guards: Replaced all asynchronoussetTimeoutreverts (which were used to prevent visual UI glitches when guarded actions were blocked) with synchronous, idiomatic Angular forcedChangeDetectorReftriggering (this.cdr.detectChanges()). - Synchronous Model Enforcement: Applied
this.cdr.detectChanges()to theiterativeTable,col.type,includeRowAggregate, andincludeColumnAggregateguards to instantly sync the rejected model state back to PrimeNG components.\
Decisions
- One-Way Intercept
[ngModel]vs Two-Way Binding[(ngModel)]:- Standardized on the single-binding intercept pattern (
[ngModel]="value"+(ngModelChange)="onChange($event)") for heavily guarded UI toggles (column/row aggregates, iterative table toggle). - Why single binding: Two-way bindings (
[(ngModel)]) temporarily mutate the source-of-truth model to an illegal state before the guard can reject and revert it. This creates a tiny window where other components or watchers might read corrupted data. Single binding fixes this by acting as an uncorruptible gatekeeper—the model is never touched until the guard explicitly approves the$eventvalue.
- Standardized on the single-binding intercept pattern (
- Why ONE
detectChanges()is enough:- Established that since the model is never updated during a rejected change (using single-binding), forcing a quick
false -> detectChanges() -> truecycle forces the visual PrimeNG component to sync to the model's true state and snap back. Angular handles the final push automatically after the event loop ends, removing the need for a second DOM refresh.
- Established that since the model is never updated during a rejected change (using single-binding), forcing a quick
Files Modified
src/ifile-teapot-web-dynaforms/components/dynaform-builder/right-panel-static/right-panel-static.component.ts— InjectedChangeDetectorRef, refactored allsetTimeoutreverts to usethis.cdr.detectChanges(), updatedonToggleIterativeTablesignature to acceptnewValuefor one-way binding.src/ifile-teapot-web-dynaforms/components/dynaform-builder/right-panel-static/right-panel-static.component.html— UpdatediterativeTabletoggle binding to pass$eventto the intercept method.
Matrix Formula Guards — Review & Refactor
Achievements
- Code Review Completed: Performed a senior-level code review of the Matrix Formula Guards implementation, focusing on simplicity, Angular-agnosticism, and redundancy.
- Redundancy Validated: Confirmed that the key-generation patterns duplicated between
getFormulaKeysForMatrixStructureandupdateFormulaFieldSuggestionsare justified and not harmful, since they serve different purposes (structural guard blocks vs building full hierarchy suggestions) and produce different data shapes. - Method Relocation (Refactoring): Extracted
getFormulaKeysForMatrixStructurefrom the 2300-lineRightPanelStaticComponent(where it didn't belong due to being pure, framework-agnostic domain logic) and moved it intoDynaformService. - References Updated: Updated all guard usages inside
right-panel-static.component.tsto correctly reference the service method viathis.state.getFormulaKeysForMatrixStructure(...). - Documentation Enhanced: Added a comprehensive JSDoc comment for
getFormulaKeysForMatrixStructureinDynaformServiceto clearly explain its purpose within the validation guards and document all the configuration parameters within theoptionsobject. - Version Control: Committed the refactoring changes and pushed to the remote
feature/df-matrix-formula-guardsbranch.
Files Modified
src/ifile-teapot-web-dynaforms/components/dynaform-builder/right-panel-static/right-panel-static.component.ts— MovedgetFormulaKeysForMatrixStructureout and updated usages.src/ifile-teapot-web-dynaforms/services/dynaform.service.ts— Added the relocatedgetFormulaKeysForMatrixStructuremethod with detailed JSDoc.