fix(edit-content): keep CATEGORY form value as array across workflow rebuilds#35530
fix(edit-content): keep CATEGORY form value as array across workflow rebuilds#35530
Conversation
…rebuilds (#35529) After firing any workflow action that re-fetches the contentlet (Reset Workflow, or any subaction that updates modDate), required CATEGORY fields visually retained their selected chips but the form control's value reverted to a CSV string. The next save coerced that string to [] in processFormValue, and the backend rejected with "field required". A secondary symptom — an Angular "no FormControl instance attached" error during form rebind — left some sibling inputs visually stuck in a disabled/loading state. Two related fixes: 1. Skip the inappropriate CSV cast for CATEGORY. categoryResolutionFn produces an array of category keys, but it was being passed through castSingleSelectableValue (which targets radio/select/checkbox) and coerced to a CSV string. Add CATEGORY to UNCASTED_FIELD_TYPES so the array shape is preserved end-to-end. This also serves as defense in depth: if the LOADED-state effect fails to upgrade keys to inodes, the form now sends an array of keys instead of an empty array. 2. Re-emit inodes on writeValue when the parent form is rebuilt. Angular's formGroup directive does not destroy the dot-category-field on rebind — the same component is reused with a new FormControl. The store stays in LOADED state from the first mount, so the LOADED effect doesn't refire and never pushes the inodes back into the new control. Override writeValue to detect this case and re-emit. The onChange call is deferred to a microtask because writeValue is invoked synchronously inside FormGroupDirective._updateDomValue -> setUpControl, BEFORE Angular assigns dir.control = newCtrl; calling onChange synchronously walks through viewToModelUpdate against the not-yet-assigned dir.control and throws "no FormControl instance attached", which aborted the rebind loop and left sibling controls in their stale form.disable() state. Verified end-to-end in the new edit-content UI: edit -> Publish -> Reset Workflow -> edit -> Publish all return 200 with the correct inodes payload, no console errors, no inputs stuck disabled.
|
Claude finished @oidacra's task in 2m 19s —— View job Code Review
1. 2.
The existing "should form control has the values" test (line 106) uses 3. function sameInodes(value: string[], inodes: string[]): boolean {
for (let i = 0; i < inodes.length; i++) {
if (value[i] !== inodes[i]) return false;
}
return true;
}If the store returns inodes in a different order from what the form received (plausible if the backend returns categories in insertion order and the form serializes differently), // [Fix this →](https://claude.ai/code?q=fix%20sameInodes%20in%20core-web%2Flibs%2Fedit-content%2Fsrc%2Flib%2Ffields%2Fdot-edit-content-category-field%2Fcomponents%2Fdot-category-field%2Fdot-category-field.component.ts%20to%20use%20Set-based%20comparison%20instead%20of%20positional%20comparison%20to%20avoid%20spurious%20onChange%20calls%20when%20order%20differs&repo=dotCMS%2Fcore)
function sameInodes(a: string[], b: string[]): boolean {
if (a.length !== b.length) return false;
const set = new Set(a);
return b.every((v) => set.has(v));
}Minor: Summary: Fix is correct and unblocks the reported regression. Items 1 (dead store method) and 2 (missing tests) are the most actionable before merge. |
Parent Issue
Fixes #35529
Proposed Changes
In the new Edit Content experience, after firing any workflow action that re-fetches the contentlet (Reset Workflow, or any subaction that updates the contentlet's
modDate), required CATEGORY fields visually retained their selected chips but the form control's value silently reverted to a CSV string. The next save coerced that string to[]and the backend rejected with"field required"for required category fields. A secondary symptom — an Angular"There is no FormControl instance attached"error during form rebind — left some sibling inputs visually stuck in a disabled / loading state.Two related fixes in
libs/edit-content:dot-edit-content-field.constant.ts— AddFIELD_TYPES.CATEGORYtoUNCASTED_FIELD_TYPES.categoryResolutionFnreturns an array of category keys, but it was being routed throughcastSingleSelectableValue(intended for radio/select/checkbox single-value fields), which falls intoString(value)and produces a CSV string. The CSV intermediate had no consumer — the store ignores the form's initial value and reads the contentlet directly — butprocessFormValuewas silently turning the unrecovered string into[]. Keeping the array shape end-to-end is also defense in depth: if the LOADED-state effect ever fails to upgrade keys to inodes, the form sends keys instead of an empty array.dot-category-field.component.ts— OverridewriteValueto re-emit inodes after a form rebuild. Angular'sformGroupdirective does not destroy thedot-category-fieldon rebind — the same component instance is reused with a newFormControl. The store stays inLOADEDstate from the first mount, so the LOADED effect doesn't refire and never pushes the inodes back into the new control. TheonChangecall is deferred to a microtask becausewriteValueis invoked synchronously insideFormGroupDirective._updateDomValue → setUpControl(...), before Angular assignsdir.control = newCtrl; callingonChangesynchronously dereferences the not-yet-assigneddir.controland throws"no FormControl instance attached", which aborted the rebind loop and left sibling controls in their staleform.disable()state.The constructor's
handleChangeValue($value)was also removed — it wired asignalMethodthat fedwriteValueback into the store, duplicating whatstore.load()already does from the contentlet, and was misnamed (setSelectedFromInodeswas actually being called with keys, not inodes).Why is this important
Quality Checklist
Additional Information
Verified end-to-end in the new edit-content UI, with
dot-category-fieldinstances on a Job Aid Article contentlet (requiredaudienceandnavigationLocation):"field required"error, no console errorsForm-control inspection after Reset Workflow:
sigValis now an array of keys instead of the previous CSV string"jobAidsA,jobAidsB"— confirming the cast is gone.Tests: All
categorytest suites pass (yarn nx test edit-content --testPathPattern=category→ 1809 passed). The single failing test in the broader edit-content run (calendar-field.util.spec.ts— "now" handling) is a pre-existing flake unrelated to this change.