@@ -3,7 +3,13 @@ import { assert, info } from '@ember/debug';
33import { get } from '@ember/-internals/metal' ;
44import { DEBUG } from '@glimmer/env' ;
55import { hasInternalComponentManager } from '@glimmer/manager' ;
6- import type { Destroyable , TemplateFactory } from '@glimmer/interfaces' ;
6+ import type { CurriedComponent , Destroyable , Template , TemplateFactory } from '@glimmer/interfaces' ;
7+ import type { Reference } from '@glimmer/reference' ;
8+ import { createComputeRef } from '@glimmer/reference' ;
9+ import { createCapturedArgs , curry , EMPTY_POSITIONAL } from '@glimmer/runtime' ;
10+ import { dict } from '@glimmer/util' ;
11+ import { tracked } from '@glimmer/tracking' ;
12+ import { makeRouteTemplate } from '@ember/-internals/glimmer/lib/component-managers/route-template' ;
713import type Route from '@ember/routing/route' ;
814import type {
915 RouteManager ,
@@ -21,10 +27,22 @@ import { Promise } from 'rsvp';
2127
2228// --- Bucket ---
2329
24- export interface ClassicRouteBucket extends RouteStateBucket {
30+ export class ClassicRouteBucket implements RouteStateBucket {
2531 route : Route ;
26- context : unknown ;
32+ @tracked context : unknown ;
33+ controller : unknown ;
2734 invokable : object | undefined ;
35+ instance : object ;
36+ args : CreateRouteArgs ;
37+
38+ constructor ( route : Route , args : CreateRouteArgs ) {
39+ this . route = route ;
40+ this . context = undefined ;
41+ this . controller = undefined ;
42+ this . invokable = undefined ;
43+ this . instance = route ;
44+ this . args = args ;
45+ }
2846}
2947
3048// --- Classic interop args ---
@@ -55,13 +73,7 @@ export class ClassicRouteManager implements RouteManager<ClassicRouteBucket> {
5573 let route = routeInstance as Route ;
5674 route . _setRouteName ( args . name ) ;
5775
58- return {
59- route,
60- context : undefined ,
61- invokable : undefined ,
62- instance : route ,
63- args,
64- } ;
76+ return new ClassicRouteBucket ( route , args ) ;
6577 }
6678
6779 getDestroyable ( bucket : ClassicRouteBucket ) : Destroyable | null {
@@ -150,7 +162,7 @@ export class ClassicRouteManager implements RouteManager<ClassicRouteBucket> {
150162 } )
151163 . then ( ( resolvedModel ) => this . _runAfterModel ( route , resolvedModel , transition ) )
152164 . then ( ( resolvedModel ) => {
153- ( bucket as any ) . context = resolvedModel ;
165+ bucket . context = resolvedModel ;
154166
155167 return resolvedModel ;
156168 } )
@@ -180,6 +192,10 @@ export class ClassicRouteManager implements RouteManager<ClassicRouteBucket> {
180192 route . setup ( context , transition ! ) ;
181193 }
182194
195+ // Sync the controller onto the bucket so that the
196+ // the invokable (see getInvokable) picks it up.
197+ bucket . controller = route . controller ;
198+
183199 if ( route . _environment ?. options ?. shouldRender !== false ) {
184200 once ( route . _router , '_setOutlets' ) ;
185201 }
@@ -212,6 +228,11 @@ export class ClassicRouteManager implements RouteManager<ClassicRouteBucket> {
212228 // --- Template/Component lookup ---
213229
214230 getInvokable ( bucket : ClassicRouteBucket ) : Promise < object | undefined > {
231+ // Return cached invokable if already built
232+ if ( bucket . invokable !== undefined ) {
233+ return Promise . resolve ( bucket . invokable ) ;
234+ }
235+
215236 let route = bucket . route ;
216237 let owner = getOwner ( route ) ;
217238 assert ( 'Route is unexpectedly missing an owner' , owner ) ;
@@ -222,12 +243,17 @@ export class ClassicRouteManager implements RouteManager<ClassicRouteBucket> {
222243 | object
223244 | undefined ;
224245
225- let template : object ;
246+ let component : object ;
247+ // Track whether the component is already a resolved definition (CurriedValue
248+ // from makeRouteTemplate) vs a raw component class that needs VM resolution.
249+ let isResolved = true ;
226250
227251 if ( templateFactoryOrComponent ) {
228252 if ( hasInternalComponentManager ( templateFactoryOrComponent ) ) {
229- // ComponentLike - pass through directly
230- template = templateFactoryOrComponent ;
253+ // ComponentLike - use directly, will be curried below.
254+ // Not resolved yet — the VM needs to look up its definition.
255+ component = templateFactoryOrComponent ;
256+ isResolved = false ;
231257 } else {
232258 if ( DEBUG && typeof templateFactoryOrComponent !== 'function' ) {
233259 let label : string ;
@@ -246,8 +272,9 @@ export class ClassicRouteManager implements RouteManager<ClassicRouteBucket> {
246272 ) ;
247273 }
248274
249- // TemplateFactory -> Template
250- template = ( templateFactoryOrComponent as TemplateFactory ) ( owner ) ;
275+ // TemplateFactory -> Template -> RouteTemplate (curried component with no args)
276+ let template = ( templateFactoryOrComponent as TemplateFactory ) ( owner ) ;
277+ component = makeRouteTemplate ( owner , name , template as Template ) ;
251278 }
252279 } else {
253280 if ( DEBUG ) {
@@ -258,11 +285,33 @@ export class ClassicRouteManager implements RouteManager<ClassicRouteBucket> {
258285 } ) ;
259286 }
260287 }
261- // Default {{outlet}} template
262- template = route . _topLevelViewTemplate ( owner ) ;
288+ // Default {{outlet}} template -> RouteTemplate
289+ let template = route . _topLevelViewTemplate ( owner ) ;
290+ component = makeRouteTemplate ( owner , name , template as Template ) ;
263291 }
264292
265- bucket . invokable = template ;
266- return Promise . resolve ( template ) ;
293+ // Create refs that read from the bucket. The bucket fields are populated
294+ // later (context during enter(), controller during didEnter()), so these
295+ // refs start as undefined and resolve lazily.
296+ //
297+ // Controller: a tag-free compute ref. It's read lazily (first read happens
298+ // during rendering, after didEnter has set bucket.controller).
299+ //
300+ // Model: bucket.context is @tracked , so this compute ref auto-tracks it
301+ // and re-evaluates when the model changes (e.g. navigating to the same
302+ // route with different dynamic segments).
303+ let named = dict < Reference > ( ) ;
304+ named [ 'controller' ] = createComputeRef ( ( ) => bucket . controller ) ;
305+ named [ 'model' ] = createComputeRef ( ( ) => bucket . context ) ;
306+
307+ let args = createCapturedArgs ( named , EMPTY_POSITIONAL ) ;
308+
309+ // Curry @controller and @model onto the component so the invokable is
310+ // self-contained. The outlet rendering pipeline no longer needs to know
311+ // about model or controller.
312+ let invokable = curry ( 0 as CurriedComponent , component , owner , args , isResolved ) ;
313+
314+ bucket . invokable = invokable ;
315+ return Promise . resolve ( invokable ) ;
267316 }
268317}
0 commit comments