Skip to content

[BUGFIX] @model becomes undefined or changes to the wrong route's model during Glimmer component willDestroy#21203

Open
johanrd wants to merge 6 commits intoemberjs:mainfrom
johanrd:test/20959
Open

[BUGFIX] @model becomes undefined or changes to the wrong route's model during Glimmer component willDestroy#21203
johanrd wants to merge 6 commits intoemberjs:mainfrom
johanrd:test/20959

Conversation

@johanrd
Copy link
Contributor

@johanrd johanrd commented Mar 8, 2026

Summary

Details

The @model reference in route templates is built from a chain of compute refs that read from the shared outlet state tree via dynamic scope. When transitioning, _setOutlets() rebuilds this tree, and all refs in the chain immediately see the new state.

The existing guard (lastState === state) freezes the model value when the outlet changes at the same level. But when a parent outlet is torn down first, the child outlet's outer compute ref never re-evaluates, so lastState is never updated. The guard passes, andmodelRef reads from the wrong outlet state.

The fix compares the current outlet's controller identity against the expected one. Since each route has a unique controller singleton, this detects when the outletRef has been redirected — even when lastState hasn't been updated yet.

From git history, the bug may have existed since @model was introduced in v3.14.0 (16b74a5). The original implementation read directly from the outletRef chain with no guard at all. The lastState === state guard added in v4.0.0 (commit 7d334cf) partially mitigated same-level transitions but did not catch parent-level teardown.

Fixes #18987
Supersedes #20959

Test plan

  • Smoke test in smoke-tests/scenarios/basic-test.ts: transitions through sibling, parent, cousin, and unrelated routes, verifying this.args.model in willDestroy Based on Add a test that verifies @model is stable between route transitions #20959 but moved to smoke-test to avoid the vite.config import of glimmer-component
  • Full internal test suite passes (5907/5907)
  • Scenario tests pass for both v1 (classic/embroider-webpack) and v2 (embroider-vite) app builds

Before the fix, the added smoke tests fail with:

not ok 1 - Acceptance | @model stability during route transitions: @model should be stable when transitioning out of the route                                                                                                                                                                                                            
      actual:   b,b,d,e,
      expected: b,b,b,b,b
      message:  The @model value should remain stable in willDestroy for all transition types
ember-source Result Notes
workspace:* (with fix) PASS Local build includes the bugfix
~6..11.0 FAIL Last version
~5.12.0 FAIL Last 5.x
~4.0.0 FAIL First 4.x
~3.28.0 FAIL Last 3.x

Cowritten by claude

Windvis and others added 4 commits March 8, 2026 01:08
When transitioning between routes, the @model argument on a Glimmer
component becomes unstable during willDestroy - the model value changes
before the component is properly destroyed.

Requires @glimmer/component Vite alias to resolve in tests.

Based on PR emberjs#20959 by @Windvis.
When transitioning between routes, @model becomes undefined in Glimmer
component willDestroy hooks. The existing guard (lastState === state)
only detects outlet changes at the same level. When a parent outlet
tears down first, the dynamic scope refs silently redirect to the new
route's outlet state while the guard still passes.

  Add a controller identity check so the model ref detects when its
  outletRef has been redirected to a different route's data.
  Fixes emberjs#18987
…nternal @glimmer/component import isn't necessary in vite.config
@kategengler kategengler requested a review from ef4 March 8, 2026 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Route @model argument value timing issue

3 participants