MemberJunction / MemberJunction/MJ

FK picker's display name goes stale after the second selection: read-only virtual fields accept one write then silently drop the rest

Open
#3,996 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TSQL
Stars
29
Forks
6
Avg merge
2d 1h
Merged PRs (30d)
323

Description

## Summary

`EntityField`'s value setter permits a write to a **read-only** field exactly once, then silently discards every later write. MJ's own FK picker writes the joined display-name field on every selection — so the **first** FK pick updates the display name and every subsequent pick is dropped with no error, leaving stale text on screen.

## The mechanism

`packages/MJCore/src/generic/baseEntity.ts` (~line 129), `EntityField.set Value`:

```ts
if (
!this.ReadOnly ||
this._NeverSet /* Allow one time set of any field because BaseEntity Object passes in ReadOnly
fields when we load, after that load for a given INSTANCE of an EntityField
object we never set a ReadOnly Field */
) { ... this._Value = value; }
```

`EntityFieldInfo.ReadOnly` is `!AllowUpdateAPI || IsPrimaryKey || IsSpecialDateField`, and its own comment names the affected case: *"truly read-only virtual fields like joined display names (AllowUpdateAPI=0)"*.

The writer is `packages/Angular/Generic/base-forms/src/lib/field/form-field.component.ts`, `selectFKSuggestion()`:

```ts
// Update virtual name field for visual consistency after save
const nameFieldMap = this.FieldInfo?.RelatedEntityNameFieldMap;
if (nameFieldMap) {
this.Record.Set(nameFieldMap, suggestion.DisplayName);
}
```

First call: `_NeverSet` is still true → the write lands. Second call: field is `ReadOnly` and no longer never-set → **`Set()` returns having done nothing.** `OnFKClearClick()` has the same problem.

## Repro

1. Open any record form with a foreign key whose `EntityField.RelatedEntityNameFieldMap` is populated (i.e. essentially every FK).
2. Pick a related record — the joined display name updates correctly.
3. Pick a **different** related record.
4. The underlying id changes; the displayed name still shows the **first** selection.
5. Save and reload — the name corrects itself, because the base view repopulates it.

Observed on `MJ_BizApps_Contracts: Contracts` → `ContractTemplateID` / `ContractTemplate` (MJ 6.1.0-edge.2/3). It is not specific to that entity: `ContractType`, `CustomerOrganization`, `PrimaryContactPerson` and `ParentContract` all have populated name-field maps and all behave the same way.

## Why it is worth fixing despite being cosmetic

- **It affects every FK on every entity**, so the blast radius is the whole app surface.
- **It is completely silent.** `Set()` neither returns a status nor logs, so the calling code believes it succeeded, and the UI shows a value that contradicts the id the user just chose.
- A user changing their mind — an ordinary action — sees the form assert something false about the record they are editing.

## Suggested fix

The name-map field is display-only and never persisted, so the one-write guard is not protecting anything here. `ResetNeverSetFlag()` is already `public` on `EntityField` (and is exactly what `NewRecord()` uses for the same purpose), so the picker can re-enable the write:

```ts
const nameFieldMap = this.FieldInfo?.RelatedEntityNameFieldMap;
if (nameFieldMap) {
this.Record.GetFieldByName(nameFieldMap)?.ResetNeverSetFlag();
this.Record.Set(nameFieldMap, suggestion.DisplayName);
}
```

Same in `OnFKClearClick()` and the inline-create path (`OnFKCreated`).

Worth considering separately: **`BaseEntity.Set()` silently ignoring a rejected write is its own hazard.** A dropped write to a read-only field is nearly always a bug in the caller, and a warning (or a returned boolean) would have surfaced this immediately rather than as a UI mystery.

Contributor guide

Open the contributing guide

Research direction

Start with packages/MJCore/src/generic/baseEntity.ts and packages/Angular/Generic/base-forms/src/lib/field/form-field.component.ts. Read EntityField.set Value, selectFKSuggestion(), OnFKClearClick(), and OnFKCreated(), then reproduce repeated FK selections and clearing on a form with RelatedEntityNameFieldMap. Done means the display-name field stays synchronized after every selection, clear, and inline-create operation.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.