Implement a grid-elements-manager widget
Let users manage a parent’s children directly from the parent’s detail view. The widget renders the child’s list (filtered to the current parent) and a ”+ New” button that opens the child’s form in a dialog — without leaving the parent.
Before you start
Section titled “Before you start”- A Catalyst project with two related frontend modules already scaffolded — a parent (e.g.
iam/bounded-context) and a child (e.g.iam/permission). - The child YAML declares a
many-to-onerelationship pointing back to the parent — that is the FK the codegen reads to wire the widget. Most child modules already have it. - The parent’s
front.detailModeis unset orview. Dialog mode cannot host the widget — the codegen warns and omits it to avoid a dialog-in-dialog UX. - You can run
catalyst generate front module --forcelocally.
-
Opt the child into embed mode. In the child YAML, declare
front.embedSupport: true. Without this flag the codegen emits the same files it always did, and the parent’s regen will fail when it tries to dispatch the widget.cliter/iam/permission.aurora.yaml front:embedSupport: true -
Regenerate the child.
Terminal window catalyst generate front module --name=iam/permission --forceThree new artefacts appear:
iam-permission-form-embed.component.ts— a form variant whose FK to the parent is injected atsubmit()time, not declared as aFormControl. The required[parentValue]input is wired by the embedded list automatically.getIamPermissionEmbedColumns(...)factory iniam-permission.columns.ts— same columns asStandalone, minus the column for the FK to the parent (every row in embed view shares the same parent, so the column would be redundant).- The list component grows
mode,parentFilter,parentDefaultsinputs and renders without header or breadcrumb inmode="embed".
-
Declare the widget on the parent property. In the parent YAML, the relationship property that points at the child gets
widget.type: grid-elements-manager. Optionally place it inside a tab.cliter/iam/bounded-context.aurora.yaml aggregateProperties:- name: permissionstype: relationshiprelationship:type: one-to-manysingularName: permissionaggregateName: IamPermissionmodulePath: iam/permissionwidget:type: grid-elements-managertab: permissions # optionalwidget.detailSortandwidget.isDetailHiddenapply normally — the widget is treated as a logical field for ordering and hiding.widget.spanis ignored: the widget always renders full-width. -
Regenerate the parent.
Terminal window catalyst generate front module --name=iam/bounded-context --forceThe codegen reads
iam/permission.aurora.yaml, finds the property whoserelationship.type === 'many-to-one'andrelationship.modulePath === 'iam/bounded-context'(e.g.boundedContextId), and emits the partial inside the parent’s detail shell — wrapped in@if (mode() === 'edit')so it only shows once the parent has an id. The list embed receivesparentFilter: { field: 'boundedContextId', value: bcId() }andparentDefaults: { boundedContextId: bcId() }. -
Add the translation key. The widget’s card title uses a key derived from the child aggregate’s plural name:
<bcKebab>.<childModKebab>.<ChildAggregatePluralPascal>For
iam/bounded-context.permissions→iam.permission.Permissions. Add the entry to your translation files; the codegen does not provide the value.
Verify it worked
Section titled “Verify it worked”- Open the parent’s detail in
editmode (/iam/bounded-context/edit/<id>). A new section appears below the form card (or inside the declared tab) with a list of child rows filtered to the current parent. - Click ”+ New” inside the embedded list. A dialog opens with the form-embed; submit creates a child whose FK to the parent is set automatically — even though the FK has no field in the form.
- Open the parent in
newmode (/iam/bounded-context/new). The widget is absent — the parent has no id yet, so there is nothing to associate children with. - The child’s standalone surface (
/iam/permission,/iam/permission/new,/iam/permission/edit/:id) keeps working unchanged.
Troubleshooting
Section titled “Troubleshooting”The parent’s regen fails with “target lacks embedSupport: true”.
The codegen reads the child YAML to validate the embedability. Add front.embedSupport: true to the child YAML and regenerate the child first (step 1–2), then rerun the parent.
Regen fails with “child has no many-to-one back-reference”.
The codegen could not find the FK property in the child YAML. Confirm the child declares an aggregateProperty with relationship.type: many-to-one and relationship.modulePath pointing at the parent’s path.
The widget never shows up.
Check the parent’s front.detailMode: if it is dialog, the codegen logs a warning and omits the widget to avoid stacking a dialog inside a dialog. Switch to view and regenerate.
The widget shows up but the rows look wrong.
The list embed reuses the child’s getXEmbedColumns(...) factory. If you customised the child’s columns expecting only the standalone factory to exist, double-check that your edits did not break the embed factory — both live in the same *.columns.ts file.
widget.span does not affect the layout.
Expected. The widget is a section, not a field — widget.span is ignored on grid-elements-manager and the codegen emits a warning when you declare it.
Related
Section titled “Related”- Grid elements manager widget — the change that introduced the widget.
- Detail mode: view or dialog — why dialog mode cannot host the widget.
- Form field widths — the grid the parent’s form uses; the widget renders as a sibling section, not as a field.
catalyst generatereference — every flag and argument.