Álvaro Franz
Álvaro Franz

Reputation: 811

Use a subscription inside a route resolver

I want to ask a service for some data, to allow the route resolver to take a specific action.

The data that should be returned by the service, is not the data that will be used by the component after the route is resolved, which is the clear use of a resolver, and which explains why the resolver must return an Observable.

What I really need the data returned by the service for, is to decide between several possible actions before the route gets resolved at all.

Why did I get into this situation?

I am in the the case where a top url with a slug parameter may need distinct components to be rendered.

Example:

example.com/whatever-post-slug --> needs to load PostComponent
example.com/other-page-slug --> needs to load PageComponent
example.com/new-slug-for-random-post --> needs to load PostComponent
...

Why don't I just prefix the route?

Because I cannot. I am creating a theme for WordPress and allowing similar urls for posts and pages. Refer to this question for more details.

What is my resolver actually doing?

I will only show partial code, to make it graspable:

resolve(route: ActivatedRouteSnapshot): Observable<any>|any{
   this.wprestNoAuthSrv.getPermalinkStructure()
       .pipe(takeUntil(this.unsubscribeOnDestroy))
       .subscribe(
       // ... check httpResponse and decide where to go
          this.router.navigate(route, { skipLocationChange: true });
       )
}

Because of the fact that I am subscribing inside the resolver, the component associated to the route being resolved, loads before navigate() takes place.

This results in a very ugly navigation effect.

The question is:

How can I stop this from happening, while still using that suscription?

I actually want that component to never load, because there will always be a navigate() going on.

If you realize, I put { skipLocationChange: true } into the router.navigate(), because I want the user to keep the path in the browser.

I understand this may seem like I am misusing Angular's routing system, but I want to achieve that result.

This question is not about the fact that I want to load distinct components dynamically (for that purpose I posted the above linked question).

This question is about the chosen approach, which is working, but with a downside that I'd like to avoid.

An idea that works:

This idea works, but maybe you know a better solution. If I create an observabe and return it in the resolve() method, I can force the resolver to wait and the redirect will already be done for the time it finishes.

return Observable.create(observer => {
  setTimeout(() => {
    observer.next("Finished");
    observer.complete();
  }, 5000);
});

Of course, this is a big lie and not very nice. And even though it works perfectly, I need to know if this is a very bad thing to do, or if there is a better alternative.

Upvotes: 2

Views: 5822

Answers (2)

Sasi Kumar M
Sasi Kumar M

Reputation: 2630

For me, the requirement was to wait until the data is fetched if not exist. Hence I followed the approach of using promise inside resolvers as shown in the sample code below.

   public resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot) {

    return new Promise((resolve, reject) => {
       this.store.getDataObservable().subscribe((data)=> {
            if(data){
              resolve(data)
            } else {
              this.store.dispatch(fetchData());
            }
         });                
    });
}

I had NgRx implementation to fetch data from API via actions, hence subscribing to the observable from store works in my situation to get the latest data whenever available. Hope this helps someone in need.

Upvotes: 1

Julius Dzidzevičius
Julius Dzidzevičius

Reputation: 11000

Looks like you are trying to redirect inside the resolver and subscribing inside an Observable is never a good idea. This should meet your expectations:

resolve(route: ActivatedRouteSnapshot): Observable<any>|any{
   return this.wprestNoAuthSrv.getPermalinkStructure().pipe(
     takeUntil(this.unsubscribeOnDestroy),
     mergeMap(httpResponse => {
       // ... check httpResponse and decide where to go
       this.router.navigate(route, { skipLocationChange: true });
       return EMPTY;
     })
   );
}

Upvotes: 3

Related Questions