4

I am trying to use a resolver in order to retrieve data depending on the given parameters the route holds.

Unfortunately, the moment I add another data stream that my data depends on the resolver never actually resolves.

If I directly return an immediately resolving value everything works fine. I debugged the situation to see that I receive all partial information but it just fails to actually resolve in the end.

Here's a quick sample. Hit me up if there's more code needed to understand the problem.

MyService:

export class MyService {
  get(bar) {
    return of(new Foo(bar));
  }
}

SecondService (This one retrieves data from the backend):

export class SecondService {
  private readonly _observable: Observable<Bar>;
  constructor() {
    this._observable = merge(
      // Other manipulations
    ).pipe(
      // other manipulations
      shareReplay(1)
    )
  }

  observable(): Observable<Bar> {
    return this._observable;
  }
}

Resolver:

export class MyResolver {
  constructor(_secondService: SecondService, _myService: MyService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Foo> {
    // Does not work - Simply does not resolve
    // return this._secondService
    //   .observable()
    //   .pipe(
    //     switchMap((bar) => this._myService.get(bar)),
    //   );

    // WORKS
    return of(new Foobar('sampleData'));
  }
}

Router:

const routes: Routes = [
  {
    path: 'someroute',
    component: SomeComponent,
    canActivate: [SomeGuard],
    resolve: {
      myData: MyResolver,
    },
  },
];

Component:

export class SomeComponent implements OnInit {
  constructor(private readonly _route: ActivatedRoute) {}

  ngOnInit() {
    this._route.data
      .subscribe((data) => {
      console.log('received:', data);
      this.myData = data;      
    });
  }
}

SomeComponent.html

<pre *ngIf="myData">
  Received: {{ myData | json }}
</pre>
Philipp Meissner
  • 5,273
  • 5
  • 34
  • 59

2 Answers2

11

The answer to my problem is rather simple and had nothing to do with subscribing to the resolved observables, as the framework already did that automagically.

In order for a resolver to finish, all the streams it depends on need to complete. If you happen to use a hot observable it is required to use another operator like take so that the stream completes at that location.

So, all the code remains the same, except that I changed the resolver to:

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Foo> {
  return this._secondService
    .observable()
    .pipe(
      take(1),
      switchMap((bar) => this._myService.get(bar)),
  );
}

@eduPeeth: Thank you for your answer/suggestions, unfortunately, it was a far more minor issue.

Philipp Meissner
  • 5,273
  • 5
  • 34
  • 59
  • 3
    Encountered the same issue with resolver. Resolver just hangs until the stream has ended. Similarly, I was using `return this.do().pipe(first());` and it needs either `take`, `first`, or anything that ends the stream before it can resolve. – Dan Aug 23 '19 at 21:01
1

Observable only get executed when you call Observable.subscribe() explicitly . I don't see anywhere in your code where you are subscribing to your Observables.

Note : Concept of resolve is related to Promise not with Observable.

Try :

export class MyResolver {
  constructor(_secondService: SecondService, _myService: MyService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Foo> {
    // Does not work - Simply does not resolve
     return this._secondService
       .observable()
       .pipe(
         take(1),
         switchMap((bar) => this._myService.get(bar)),
       }     
       );

    // WORKS
    return of(new Foobar('sampleData')).pipe(
         take(1),
         switchMap((bar) => this._myService.get(bar)),          
     });
  }
}

Subscribe where you need the result.

eduPeeth
  • 1,840
  • 13
  • 21
  • That's correct, I am not explicitly calling `.subscribe()` on any of the given observables. I thought this was done _magically_ by angular since the documentation does not state it anywhere (https://angular.io/guide/router#route-definition-with-a-parameter). If anything, I subscribe to the router's data within my target component to retrieve the given data that has been resolved. I added the component in question to my initial post. – Philipp Meissner Jul 12 '18 at 13:18
  • 1
    You need to subscribe each and every Observable at some or other place where you are expecting a value returned from your Observable. Kindly think about your requirement once and try to subscribe. Try to update the code , if you still face issue , help you out. – eduPeeth Jul 12 '18 at 13:28
  • I have no idea how I could possibly add your suggestion. How would I `subscribe()` to my custom resolver if angular itself does instantiate the resolver and passes the data further to the component. As described in the question, the data *is* in fact flowing, the resolver just does not resolve in the end. – Philipp Meissner Jul 12 '18 at 13:34
  • 1
    Added some code snippet try and see are you getting results or not? – eduPeeth Jul 12 '18 at 16:11
  • 1
    As described in my own answer, it was a missing `take()` execution that caused the problem. In the end I was subscribing to the resolved Observable within my component already. This is also where I needed the results so subscribing within the resolver would have left me with the same problem again - No data within my component. Anyways, thank you for your time, suggestions and help! – Philipp Meissner Jul 13 '18 at 07:25