@@ -11,9 +11,10 @@ import {
1111 Resource ,
1212 effect ,
1313 DestroyRef ,
14+ EnvironmentInjector ,
1415} from '@angular/core' ;
1516import { OperatorFunction , pipe } from 'rxjs' ;
16- import { ResourceContext , ResourceResult } from '../models' ;
17+ import { ResourceContext , ResourceResult , Route } from '../models' ;
1718import { NavigationTransition } from '../navigation_transition' ;
1819import { ActivatedRoute , ActivatedRouteSnapshot , initializeActivatedRoute } from '../router_state' ;
1920import { TreeNode } from '../utils/tree' ;
@@ -77,24 +78,20 @@ function processRoute(
7778 if ( newlyCreatedRoutes . has ( route ) ) {
7879 // This route is new. We need to run its resources function once.
7980 resourceSetupPromises . push (
80- setupNewRouterResources ( route . _futureSnapshot , route , abortSignal , blockingResourcePromises ) ,
81+ setupNewRouterResources ( route , resources , abortSignal , blockingResourcePromises ) ,
8182 ) ;
8283 } else {
8384 updateExistingResources ( route , blockingResourcePromises , abortSignal ) ;
8485 }
8586}
8687
8788async function setupNewRouterResources (
88- snapshot : ActivatedRouteSnapshot ,
8989 route : ActivatedRoute ,
90+ resourcesFn : NonNullable < Route [ 'resources' ] > ,
9091 abortSignal : AbortSignal ,
9192 blockingResourcePromises : Promise < void > [ ] ,
9293) {
93- const resourcesFn = snapshot ?. routeConfig ?. resources ;
94- const parentInjector = snapshot ?. _environmentInjector ;
95- if ( ! resourcesFn || ! parentInjector ) {
96- return ;
97- }
94+ const parentInjector = route . _futureSnapshot . _environmentInjector ;
9895
9996 let childInjector = route . _localInjector ;
10097 if ( ! childInjector ) {
@@ -109,37 +106,27 @@ async function setupNewRouterResources(
109106 data : route . dataSignal ,
110107 } ;
111108
112- const resourceResultRaw = runInInjectionContext ( childInjector , ( ) => resourcesFn ( context ) ) ;
113- let resourceResult : ResourceResult ;
114- if ( resourceResultRaw instanceof Promise ) {
115- resourceResult = await resourceResultRaw ;
116- // Bail out if the router cancelled the navigation (and destroyed our injector!)
117- // while we were waiting.
118- if ( abortSignal . aborted ) return ;
119- } else {
120- resourceResult = resourceResultRaw as ResourceResult ;
121- }
122-
123- if ( ! resourceResult ) return ;
109+ const resourceResult = await runInInjectionContext ( childInjector , ( ) => resourcesFn ( context ) ) ;
110+ if ( abortSignal . aborted || ! resourceResult ) return ;
124111
125112 const wrappedResult : ResourceResult = { } ;
126- for ( const [ key , res ] of Object . entries ( resourceResult ) ) {
127- if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
113+ runInInjectionContext ( childInjector , ( ) => {
114+ for ( const [ key , res ] of Object . entries ( resourceResult ) ) {
128115 if (
129- ! res ||
130- typeof res !== 'object' ||
131- typeof ( res as Partial < Resource < unknown > > ) . snapshot !== 'function'
116+ ( typeof ngDevMode === 'undefined' || ngDevMode ) &&
117+ ( ! res ||
118+ typeof res !== 'object' ||
119+ typeof ( res as Partial < Resource < unknown > > ) . snapshot !== 'function' )
132120 ) {
133121 throw new Error (
134122 `Invalid resource returned for key "${ key } ". Expected a Resource, but got ${ res === null ? 'null' : typeof res } .` ,
135123 ) ;
136124 }
125+ wrappedResult [ key ] = routerResource ( res ) ;
137126 }
127+ } ) ;
138128
139- wrappedResult [ key ] = runInInjectionContext ( childInjector , ( ) => routerResource ( res ) ) ;
140- }
141-
142- route . resources = route . _futureSnapshot . resources = snapshot . resources = wrappedResult ;
129+ route . resources = route . _futureSnapshot . resources = wrappedResult ;
143130 setupBlocking ( route , wrappedResult , blockingResourcePromises , abortSignal ) ;
144131}
145132
@@ -155,15 +142,15 @@ function updateExistingResources(
155142 return ;
156143 }
157144
158- Object . values ( currentResources ) . forEach ( ( r ) => {
145+ for ( const r of Object . values ( currentResources ) ) {
159146 const underlyingRes = ( r as InternalRouterResource ) [ SOURCE_RESOURCE_SYMBOL ] ;
160147 if ( underlyingRes . status ( ) === 'error' ) {
161148 // If a resource previously failed and the route is reused identically,
162149 // the parameter signals won't change, meaning the internal effect won't automatically refetch.
163150 // We must manually trigger a reload to ensure the new navigation attempts a retry.
164- ( underlyingRes as unknown as { reload ?: ( ) => boolean } ) . reload ?.( ) ;
151+ ( underlyingRes as Partial < InternalRouterResource > ) . reload ?.( ) ;
165152 }
166- } ) ;
153+ }
167154
168155 route . _futureSnapshot . resources = currentResources ;
169156 setupBlocking ( route , currentResources , blockingResourcePromises , abortSignal ) ;
@@ -179,52 +166,53 @@ function setupBlocking(
179166 const childInjector = route . _localInjector ;
180167 if ( ! childInjector || ! resourceResult ) return ;
181168
182- for ( const r of Object . values ( resourceResult ) ) {
183- const res = r as InternalRouterResource ;
184- if ( res [ BLOCKING_SYMBOL ] = == false ) {
185- continue ;
169+ for ( const res of Object . values ( resourceResult ) ) {
170+ const internalRes = res as InternalRouterResource ;
171+ if ( internalRes [ BLOCKING_SYMBOL ] ! == false ) {
172+ blockingResourcePromises . push ( waitForResource ( internalRes , childInjector , abortSignal ) ) ;
186173 }
187- const promise = new Promise < void > ( ( resolve , reject ) => {
188- const underlyingRes = res [ SOURCE_RESOURCE_SYMBOL ] ;
189- let isDestroyed = false ;
190- let unregisterOnDestroy : ( ( ) => void ) | undefined ;
191-
192- const cleanup = ( ) => {
193- isDestroyed = true ;
194- blockingEffect . destroy ( ) ;
195- unregisterOnDestroy ?.( ) ;
196- abortSignal . removeEventListener ( 'abort' , onAbort ) ;
197- } ;
174+ }
175+ }
198176
199- const onAbort = ( ) => {
200- cleanup ( ) ;
201- resolve ( ) ;
202- } ;
177+ function waitForResource (
178+ resource : InternalRouterResource ,
179+ injector : EnvironmentInjector ,
180+ abortSignal : AbortSignal ,
181+ ) : Promise < void > {
182+ return new Promise < void > ( ( resolve , reject ) => {
183+ const underlyingRes = resource [ SOURCE_RESOURCE_SYMBOL ] ;
184+ let isDestroyed = false ;
185+ let unregisterDestroy : ( ( ) => void ) | undefined ;
186+
187+ const cleanup = ( ) => {
188+ isDestroyed = true ;
189+ blockingEffect . destroy ( ) ;
190+ unregisterDestroy ?.( ) ;
191+ abortSignal . removeEventListener ( 'abort' , onDone ) ;
192+ } ;
193+
194+ const onDone = ( ) => {
195+ cleanup ( ) ;
196+ resolve ( ) ;
197+ } ;
198+
199+ abortSignal . addEventListener ( 'abort' , onDone , { once : true } ) ;
200+
201+ const blockingEffect = effect (
202+ ( ) => {
203+ if ( isDestroyed ) return ;
204+ const status = underlyingRes . status ( ) ;
205+ if ( status === 'error' ) {
206+ cleanup ( ) ;
207+ reject ( underlyingRes . error ( ) ) ;
208+ } else if ( ! underlyingRes . isLoading ( ) ) {
209+ cleanup ( ) ;
210+ resolve ( ) ;
211+ }
212+ } ,
213+ { injector, manualCleanup : true } ,
214+ ) ;
203215
204- abortSignal . addEventListener ( 'abort' , onAbort , { once : true } ) ;
205-
206- const blockingEffect = effect (
207- ( ) => {
208- if ( isDestroyed ) {
209- return ;
210- }
211- const status = underlyingRes . status ( ) ;
212- if ( status === 'error' ) {
213- cleanup ( ) ;
214- reject ( underlyingRes . error ( ) ) ;
215- } else if ( ! underlyingRes . isLoading ( ) ) {
216- cleanup ( ) ;
217- resolve ( ) ;
218- }
219- } ,
220- { injector : childInjector , manualCleanup : true } ,
221- ) ;
222-
223- unregisterOnDestroy = childInjector . get ( DestroyRef ) . onDestroy ( ( ) => {
224- cleanup ( ) ;
225- resolve ( ) ;
226- } ) ;
227- } ) ;
228- blockingResourcePromises . push ( promise ) ;
229- }
216+ unregisterDestroy = injector . get ( DestroyRef ) . onDestroy ( onDone ) ;
217+ } ) ;
230218}
0 commit comments