mj-entity-data-grid renders ZERO columns when a stale cross-entity grid state matches no fields — rows load, count is correct, nothing displays
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 84/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- angular, typescript
- Domain
- frontend
Research direction
Start in packages/Angular/Generic/entity-viewer/src/lib/entity-data-grid/entity-data-grid.component.ts, reading buildAgColumnDefs() and buildAgColumnDefsFromGridState() around the mentioned lines. Verify that stale state with no matching fields falls through to the existing _columns or entity fallback, then confirm rebinding between disjoint entities renders headers and rows instead of an empty grid.
Written by the indexing model from the issue text.
Description
Summary
mj-entity-data-grid accepts a zero-column result from a stale, cross-entity grid state and
renders it — producing a grid that loads its rows, reports the correct row count, and displays
no columns and therefore nothing at all. There is no error and no console warning.
The correctly-built column list is sitting in the very next branch of the same if, unused.
Repro
- Host one
<mj-entity-viewer>and rebind[EntityName]between two entities that share no
DefaultInViewfield names. (We usedMJ: Animals→MJ: Care Logs.) - Load entity A and let its grid render — this populates the grid state's
columnSettings. - Rebind to entity B.
- Entity B's toolbar shows its correct row count; the grid shows no header row and no rows.
Measured, live in the browser
Read off the component with ng.getComponent() on the failing page:
entity: "MJ: Care Logs"
agRowsInDom: 21 ← the rows ARE loaded and in the DOM
columnsBuilt: 6 ← _columns was built correctly from metadata
agColumnDefs: 0 ← …and then discarded
agHeaderCells: 0
defaultInViewTrue: 6 ← metadata is correct
truthyButNotTrue: 0 ← and they are real booleans, not 1/truthy
gridStateColumnNames: ["Name","Species","IntakeDate","Status","Breed","Housing"] ← ANIMAL's columns
ownFieldNames: ["ID","AnimalID","CareDate","CareType","Description","PerformedBy",
"IsComplete","FollowUpDate","Notes","__mj_CreatedAt","__mj_UpdatedAt","Animal"]
The server side was fully eliminated first: rows returned, entity permissions, DefaultInView
metadata, and the serialized GetDatasetByName('MJ_Metadata') payload the browser actually
receives (six DefaultInView: true, typeof boolean). All correct.
Root cause — two spots, both in packages/Angular/Generic/entity-viewer/src/lib/entity-data-grid/entity-data-grid.component.ts
1. buildAgColumnDefsFromGridState() (~line 2526) drops any setting whose field the current
entity does not have, with no floor when every setting is dropped:
for (const colConfig of sortedColumns) {
const field = this._entityInfo.Fields.find(f =>
f.Name.toLowerCase() === colConfig.Name.toLowerCase()
);
if (!field) continue; // all 6 of Animal's settings miss → cols === []
...
}
2. buildAgColumnDefs() (~line 2390) takes that empty array as the answer:
if (this._gridState?.columnSettings?.length && this._entityInfo) {
this.agColumnDefs = this.buildAgColumnDefsFromGridState(this._gridState.columnSettings);
} else if (this._columns.length > 0) { // ← the correctly-built 6, never reached
this.agColumnDefs = this._columns.map(col => this.mapColumnConfigToColDef(col));
} else if (this._entityInfo) {
this.agColumnDefs = this.generateAgColumnDefs(this._entityInfo);
} else {
this.agColumnDefs = [];
}
Suggested fix
Treat an empty result from the grid-state branch as no usable state and fall through:
const fromState = (this._gridState?.columnSettings?.length && this._entityInfo)
? this.buildAgColumnDefsFromGridState(this._gridState.columnSettings)
: [];
if (fromState.length) this.agColumnDefs = fromState;
else if (this._columns.length > 0) this.agColumnDefs = this._columns.map(c => this.mapColumnConfigToColDef(c));
else if (this._entityInfo) this.agColumnDefs = this.generateAgColumnDefs(this._entityInfo);
else this.agColumnDefs = [];
This is consistent with code already in the same file. generateAgColumnDefs() (~line 2620)
has exactly this kind of floor — "Fallback: if no DefaultInView fields are defined, show first 10
non-system fields". The grid-state branch is the one path missing one.
Two reasons this is worth more than the empty grid we hit
1. The partial case is worse than the total one, because nobody notices. The failure degrades by
field-name overlap, not by erroring. In the same app, Animals → Breeds looked fine because
those two share Name and Species, so two of four columns survived — a silently truncated grid
that reads as normal. Only Animals → Care Logs, which shares nothing, collapsed to zero and
became visible. Every partial-overlap pair in every app is currently showing a subset of its columns
with no indication.
2. Rebinding [EntityName] is a supported pattern, not misuse. EntityName is a rebindable
@Input, and the Entity setter already has explicit entityChanged handling that clears
viewTypeConfigById, resets InternalSortState, and nulls dynamicRendererRef — with a comment
naming this exact symptom:
"Keeping the old entity's config applies its columnSettings to the new entity, so only fields
common to both survive (e.g. just Name/Description) — the 'no/too-few columns' symptom."
So the intent to handle entity switching is clearly there and was implemented in several places; the
child grid's _gridState just isn't covered by it. A secondary question for whoever picks this up:
should EntityViewerComponent also be clearing the child grid's _gridState on entity change, in
addition to the grid defending itself?
Workaround for anyone hitting this now
Force the viewer to be destroyed and recreated when the entity changes, so no state crosses
entities — e.g. in Angular 17+ control flow, key the block on the page identity:
@for (page of activePageAsList; track page.id) {
<mj-entity-viewer [EntityName]="page.entityName" …></mj-entity-viewer>
}
That prevents state crossing entities but does not address the underlying acceptance of an empty
column set, which still bites any host that legitimately reuses one viewer.
Environment
MJ v6.1.0-edge.4 (commit 43217fa139). Found while building MJ Academy (a MemberJunction
teaching course) — a left-rail category whose sub-pages are separate entity grids sharing one viewer.
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 295
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from MemberJunction/MJ
-
next-protect requires only one status check, so PRs merge red and break the branch for everyone Open
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
MemberJunction/MJ#4609 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
MemberJunction/MJ#4603 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
MemberJunction/MJ#4570 ·
-
bug priority: high
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
MemberJunction/MJ#4548 ·
-
bug priority: high
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
MemberJunction/MJ#4547 ·
All issues in MemberJunction/MJ
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
gitbutlerapp/gitbutler#15998 · 1 comment ·
-
t/bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
bug ready-for-dev
Difficulty 1/5 1-3 hours Newbie friendliness 90/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 92/100