MemberJunction / MemberJunction/MJ

NG0100 flood on every Explorer page: ChatOverlayTopBoundaryPx reads the DOM during change detection

Open
#4,377 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

`MJExplorerAppComponent.ChatOverlayTopBoundaryPx` is a getter that reads live DOM state on every change-detection pass. The value it reads is written by a *sibling* component, so it changes between Angular's main pass and its dev-mode verification pass — producing an `NG0100` that cannot self-stabilise and therefore repeats forever.

The result is hundreds of identical errors on **every Explorer page** in a dev build.

```
ERROR RuntimeError: NG0100: ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked.
Previous value: '95'. Current value: '60'.
Expression location: _MJExplorerAppComponent component
at MJExplorerAppComponent_Conditional_2_Conditional_0_Conditional_5_Template
(explorer-app.component.html:31:48)
```

## Reproduce

1. Run MJExplorer in a dev build with the floating chat overlay enabled (`IsChatOverlayReady && ShowChatOverlay`).
2. Open any Explorer page.
3. Open the browser console.

Errors accumulate several per change-detection cycle, indefinitely.

Observed on MJ **6.1.0-edge.4** (`43217fa139`), real Chrome.

## Root cause

`packages/Angular/Explorer/explorer-app/src/lib/explorer-app.component.html:31` binds:

```html
[TopBoundaryPx]="ChatOverlayTopBoundaryPx"
```

`packages/Angular/Explorer/explorer-app/src/lib/explorer-app.component.ts:130-135`:

```ts
public get ChatOverlayTopBoundaryPx(): number {
const bannerHeight = parseFloat(
getComputedStyle(document.documentElement).getPropertyValue('--mj-connectivity-banner-height')
) || 0;
return MJExplorerAppComponent.SHELL_HEADER_PX + bannerHeight; // SHELL_HEADER_PX = 60
}
```

`--mj-connectivity-banner-height` is set on `` by `ServerConnectivityBannerComponent` (`packages/Angular/Explorer/explorer-core/src/lib/server-connectivity/server-connectivity-banner.component.ts:70`) — a different component, outside this component's change-detection pass.

So Angular reads `95` (60 + a 35px banner) on the main pass and `60` (60 + 0) on the verification pass. This is the textbook `NG0100` shape: **a template binding computed from mutable external state rather than from component state.**

## Severity — deliberately calibrated

`NG0100` is **dev-mode only**; the verification pass is stripped from production builds. There is **no user-facing impact and no visual defect** — the overlay positions correctly. This is not urgent on those grounds.

What makes it worth fixing is second-order:

1. **It buries real errors.** Hundreds of identical lines make the console unusable for diagnosing anything else. It cost real debugging time on our side, where an unrelated silent failure was masked by the noise.
2. **It breaks MJ's own test doctrine.** `TEST-PROTOCOL.md` specifies an exploratory GUI control-walk that **fails on any captured `console.error` / `pageerror`** — described there as the keystone that catches new bugs. That harness cannot pass on *any* Explorer page while this fires.

## Suggested fix

**Option 1 (preferred) — drop the JS round-trip; let CSS do it.**

The value is already expressible as pure CSS, and MJ already does exactly this elsewhere. `packages/Angular/Explorer/explorer-core/src/lib/shell/components/header/app-switcher.component.css:68`:

```css
top: calc(60px + var(--mj-connectivity-banner-height, 0px) + var(--mj-space-6));
```

Give the overlay a CSS custom property or class instead of a `TopBoundaryPx` number input, and no binding ever has to observe the banner.

**Option 2 — if the numeric input must stay, make it component state.**

Have `ServerConnectivityBannerComponent` publish its height via a service or `@Output`; store it in a field on `MJExplorerAppComponent`; update it in a `ResizeObserver`/subscription callback followed by `markForCheck()`. The binding then reads a stable field.

## Note

A second `NG0100` of the same class was observed the same day at `conversation-chat-area.component.html:110` (`_ConversationChatAreaComponent`, `'true'` → `'false'` — the boolean `[Disabled]="isProcessing"`), but only while that component was stuck in its "Creating your conversation…" state because an embedding host had not wired `(conversationCreated)`. Once the host wiring was corrected, only the `explorer-app` error remained. Reporting it here for completeness — it may have been a symptom of the stuck state rather than an independent defect, and is **not** claimed as a live bug.

Contributor guide

Open the contributing guide

Research direction

Start with the binding in packages/Angular/Explorer/explorer-app/src/lib/explorer-app.component.html:31 and the getter in explorer-app.component.ts:130-135, then compare the CSS approach in explorer-core/src/lib/shell/components/header/app-switcher.component.css:68. Check TEST-PROTOCOL.md and reproduce an Explorer page in a dev build; done means the repeated NG0100 errors no longer appear and the overlay remains correctly positioned.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, css, typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.