@@ -25,6 +25,7 @@ import {
2525 ViewChildren ,
2626 ɵConsole as Console ,
2727 ɵNoopNgZone as NoopNgZone ,
28+ DestroyRef ,
2829} from '@angular/core' ;
2930import { ComponentFixture , fakeAsync , inject , TestBed , tick } from '@angular/core/testing' ;
3031import { By } from '@angular/platform-browser/src/dom/debug/by' ;
@@ -74,7 +75,7 @@ import {
7475 UrlTree ,
7576} from '@angular/router' ;
7677import { RouterTestingHarness } from '@angular/router/testing' ;
77- import { concat , EMPTY , firstValueFrom , Observable , Observer , of , Subscription } from 'rxjs' ;
78+ import { concat , EMPTY , firstValueFrom , Observable , Observer , of , Subject , Subscription } from 'rxjs' ;
7879import { delay , filter , first , last , map , mapTo , takeWhile , tap } from 'rxjs/operators' ;
7980
8081import {
@@ -3619,6 +3620,49 @@ for (const browserAPI of ['navigation', 'history'] as const) {
36193620 } ) ;
36203621 describe ( 'guards' , ( ) => {
36213622 describe ( 'CanActivate' , ( ) => {
3623+ describe ( 'guard completes before emitting a value' , ( ) => {
3624+ @Injectable ( { providedIn : 'root' } )
3625+ class CompletesBeforeEmitting {
3626+ private subject$ = new Subject < boolean > ( ) ;
3627+
3628+ constructor ( destroyRef : DestroyRef ) {
3629+ destroyRef . onDestroy ( ( ) => this . subject$ . complete ( ) ) ;
3630+ }
3631+
3632+ // Note that this is a simple illustrative case of when an observable
3633+ // completes without emitting a value. In a real-world scenario, this
3634+ // might represent an HTTP request that never emits before the app is
3635+ // destroyed and then completes when the app is destroyed.
3636+ canActivate ( route : ActivatedRouteSnapshot , state : RouterStateSnapshot ) {
3637+ return this . subject$ ;
3638+ }
3639+ }
3640+
3641+ it ( 'should not thrown an unhandled promise rejection' , fakeAsync (
3642+ inject ( [ Router ] , async ( router : Router ) => {
3643+ const fixture = createRoot ( router , RootCmp ) ;
3644+
3645+ const onUnhandledrejection = jasmine . createSpy ( ) ;
3646+ window . addEventListener ( 'unhandledrejection' , onUnhandledrejection ) ;
3647+
3648+ router . resetConfig ( [
3649+ { path : 'team/:id' , component : TeamCmp , canActivate : [ CompletesBeforeEmitting ] } ,
3650+ ] ) ;
3651+
3652+ router . navigateByUrl ( '/team/22' ) ;
3653+
3654+ // This was previously throwing an error `NG0205: Injector has already been destroyed`.
3655+ fixture . destroy ( ) ;
3656+
3657+ // Wait until the event task is dispatched.
3658+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
3659+ window . removeEventListener ( 'unhandledrejection' , onUnhandledrejection ) ;
3660+
3661+ expect ( onUnhandledrejection ) . not . toHaveBeenCalled ( ) ;
3662+ } ) ,
3663+ ) ) ;
3664+ } ) ;
3665+
36223666 describe ( 'should not activate a route when CanActivate returns false' , ( ) => {
36233667 beforeEach ( ( ) => {
36243668 TestBed . configureTestingModule ( {
0 commit comments