angularrxjsobservableangular-route-guardsrxjs-subscriptions

rxjs observable unsubscribe in CanActivate Guard


I have one stupid question maybe: I'm implementing some routes guards that contains some other observable subscription, they are provided in root, so they should be singleton, no need to unsubscribe really. But what I'm doing is returning all the time a new Observable that I completes manually (not sure if this is required), the internal subscription are cleared each execution with the private _sub field.

Question is: is this code safe from memory leaks (how is routing guard subscribed internally to this observable)? there is a recommended way to test and see if there are observables not unsubscribed in one application ?

@Injectable({
   providedIn: 'root'
})

export class MyRedirectGuard implements CanActivate {
   private _sub: Subscription;

   constructor(private myService: MyService,
               private router: Router) {
   } 

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
  if (this._sub)
     this._sub.unsubscribe();

       return new Observable<boolean | UrlTree>((observer: any) => {

        this._sub = this.myService.serviceReady$.subscribe({
           next: ready => {
              //Navigate to Overview or to specific machine if single station
              if (ready) {
                 //some  other code to redirect
                 //if (condition) this.router.navigate(['mainPage'])
                 //else this.router.navigate(['secondPage'])
                 observer.next(false);

                 observer.complete();

              }
           }
        }
     );
   });
  }
}

Solution

  • Saying the observable is provided in root so it is a singleton therefore I don't need to unsubscribe is completely the wrong way around. The fact that it is a singleton means this is where you really need to unsubscribe. If an observable falls to garbage collection then unsubscribing is not important, but a root provided service, this is where you cause memory leaks by not managing subscriptions.

    This why my first advise to new Angular devs is always learn RxJs before you start to learn Angular. Subscribing to an observable and constructing a new observable is basically a map. The thing about mapping an observable to a new observable is you wont need to manage any subscriptions.

    This really doesn't make much sense to me but it seems your route guard will wait for the service to be ready then based on another condition redirect to one page or another.

    @Injectable({
       providedIn: 'root'
    })
    
    export class MyRedirectGuard implements CanActivate {
       constructor(private myService: MyService,
                   private router: Router) {
       } 
    
    canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
        return this.myService.serviceReady$.pipe(
          filter(ready => ready), // filter out the service not being ready
          map(_ => { // We got through the filter so we know it's ready
            //some  other code to redirect
            //if (condition) this.router.navigate(['mainPage'])
            //else this.router.navigate(['secondPage'])
            return false;
          })
        );
      }
    }
    

    Again this doesn't make sense to me why you would use a route guard like this. You are waiting for a service to be ready then always denying the route and redirecting on a different condition. I would not allow a pull request that come through with this sort of thing. Seems like that routing logic belongs somewhere else.

    Anyway, I hope my answer helps you understand that mapping one observable to another is easier than subscribing, constructing a new observable and then managing the subscription.