4
votes

I am looking for a way to terminate an async route resolve.

The scenario is the user clicks a link that fetches data necessary for a component, and while that is resolving the user hits back, or clicks on another link. I would then want to cancel the current resolve and begin the new routing.

I can't intercept the routing by listening for these changes as no route event subscription fires while the existing route (still in the resolve phase) is processing.

For example my router may look like:

const appRoutes: Routes = [
{
    path: 'resolvableRoute',
    component: ResolvableRouteComponent,
    resolve: {
      data: RouteResolver
    }
}];

And the resolver:

@Injectable()
export class RouteResolver implements Resolve<any> {

    constructor(private http: HttpClient) {}

    public resolve(router: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
        return this.http.get('api/path/for/data');
    }
}

Until that http call returns in any way, the router is more or less stuck.

I have a nice loader that starts up while the route is transitioning, and I don't want to display the new component until I have the data necessary for that component, but I need to be able to terminate the request.

For example if I did not use the resolver, the user can navigate away from a component while that component is fetching data (on init for example) as I can then just destruct the component and terminate the http request. The downside to doing the component this way is I either have to show a blank page, or an incomplete page while I am in the processing of fetching data.

2
did you tried canDeactivate property of route?. It could be used to terminate the request.mohit uprim
CanDeactivate only works when the route is activated. I need to terminate the request while the routing states are still firing. Essentially between the ResolveStart and ResolveEnd.crowebird
may be this answer is useful..stackoverflow.com/questions/36490926/…mohit uprim

2 Answers

1
votes

this worked well for me (angular 7)

Resolver

public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
    return new Observable((observer) => {
      this.http.get().pipe(
        tap(data => {
          if (data !== undefined) {
            observer.next(data);
          }
          else {
            observer.unsubscribe();
          }
          observer.complete();
        })
      ).subscribe();
    });
  }

Component

public ngOnInit() {
    this.route.data.subscribe((parameters: { data: { objs: MyObjs[] } }) => {
      if (parameters.data) {
        this.dataSource = new MatTableDataSource(parameters.data.objs);
        this.dataSource.sort = this.sort;
      }
    });
  }

Service

  public get(): Observable<any> {
    return this.http.get<any>(`${environment.apiUrl}/api/get`).pipe(
      map((response: ApiResponse) => {
        return response.data    
      }),
      catchError(err => of(undefined))
    );
  }
0
votes

I am going to post my solution for now as an answer as this work around does what I need it to and maybe it will also help someone else.

The downside to this solution is I need wrappers for the router which I would prefer to not need.

Ideally I could have just created a subscription to the router navigation queue but all those methods/variables are private in the angular router so I had no way to access them.

Instead I created a RouterService that lets me trigger an event when a new navigation event fires:

@Injectable()
export class RouterService {

    protected navigateListener = new EventEmitter<any>();

    constructor(private router: Router) {}

    public subscribe(generatorOrNext?: any, error?: any, complete?: any) {
        return this.navigateListener.subscribe(generatorOrNext, error, complete);
    }

    public navigate(commands: any[], extras?: NavigationExtras): Promise<boolean> {
        this.navigateListener.next();
        return this.router.navigate(commands, extras);
    }

    public navigateByUrl(url: string | UrlTree, extras?: NavigationExtras): Promise<boolean> {
        this.navigateListener.next();
        return this.router.navigateByUrl(url, extras);
    }

}

Then inside of my Resolve I created an observable (that normally wants to return the http request), but is also listening for an event change on the RouterService. If the route navigation is emitted, then I terminate the http request, and let the promise know it can finish...

public resolve(router: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<any> {
        return new Promise((resolve: any, reject: any) => {
                const subscription = this.http.get('/path/to/some/url').subscribe((data) => {
                    resolve(data)
                }, (error) => {
                    reject({
                        ngNavigationCancelingError: true
                    });
                });

                this.router.subscribe(change => {
                    subscription.unsubscribe();
                    reject({
                        ngNavigationCancelingError: true
                    });
                });
        });
    }