32

After the release of Angular 2 RC.5 there was introduced router resolve. Here demonstrated example with Promise, how to do the same if I make a request to the server with Observable?

search.service.ts

searchFields(id: number) {
  return this.http.get(`http://url.to.api/${id}`).map(res => res.json());
}

search-resolve.service.ts

import { Injectable } from '@angular/core';
import { Router, Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';

import { SearchService } from '../shared';

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

  constructor(
    private searchService: SearchService ,
    private router: Router
  ) {}

  resolve(route: ActivatedRouteSnapshot): Observable<any> | Promise<any> | any {
    let id = +route.params['id'];
    return this.searchService.searchFields(id).subscribe(fields => {
      console.log('fields', fields);
      if (fields) {
        return fields;
      } else { // id not found
        this.router.navigate(['/']);
        return false;
      }
    });
  }
}

search.component.ts

ngOnInit() {
  this.route.data.forEach((data) => {
    console.log('data', data);
  });
}

Get Object {fields: Subscriber} instead of real data.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Rakhat
  • 4,783
  • 4
  • 40
  • 50

1 Answers1

54

Don't call subscribe() in your service and instead let the route subscribe.

Change

return this.searchService.searchFields().subscribe(fields => {

to

import 'rxjs/add/operator/first' // in imports

return this.searchService.searchFields().map(fields => {
  ...
}).first();

This way an Observable is returned instead of a Subscription (which is returned by subscribe()).

Currently the router waits for the observable to close. You can ensure it gets closed after the first value is emitted, by using the first() operator.

markgoho
  • 69
  • 2
  • 11
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks Günter. I was looking for a way to get it to return an observable. `.map` makes sense. However, now that I'm using `.map` in my resolver, the route never seems to resolve. I do get into my `.map` function in my resolver and I see my data there, but I never get into ngOnInit of my component. My resolver looks like this `return this.eventService.getEvents().map(events => events)` and my route looks like this `..., resolve: {events: EventListResolver}}`. See anything obvious that I'm doing wrong? – Jim Cooper Aug 29 '16 at 17:01
  • 3
    @JimCooper Currently the router waits for the observable to close. You can ensure it gets closed after the first value is emitted by using the `take()` operator. I updated my questin. – Günter Zöchbauer Aug 29 '16 at 17:06
  • 1
    Ahh, that makes sense. I suppose I could also fix it by making sure my `getEvents()` method terminates my observable sequence (i.e. triggering onComplete). Thanks! – Jim Cooper Aug 29 '16 at 17:19
  • I am getting error TypeError: this.cs.getCrisis(...).map(...).first is not a function. – nurp Nov 01 '16 at 23:15
  • 4
    Ok, figured it out with import 'rxjs/add/operator/first'; – nurp Nov 02 '16 at 11:43
  • Sorry, missed your previous comment. SO isn't very helpful in tracking what comments are read :-/ Glad to hear you figured it out. – Günter Zöchbauer Nov 02 '16 at 11:45
  • I assume that `last` operator should work the same way, but in that case the route is not resolved, what is the difference? – teone Dec 09 '16 at 01:58
  • 1
    I don't think `last` can work. You need `first` or `take(1)` because the observable isn`t closed. Until it's closed there is no last event. – Günter Zöchbauer Dec 09 '16 at 04:25
  • 3
    Normally you would be making a `http` request in a route resolve service which won't require `first()` or `take(1)` because the `http` request completes after the first value in the sequence by definition. But thats not the case when using these other sources of data or when merging something like an `http` request with something like `router` observables (which don't complete). – seangwright Feb 01 '17 at 22:52
  • 2
    In relation to this question, since this answer is using `map` instead of `subscribe` where `subscribe` has an error handler, how do we now check if it failed in the route resolver so we can reroute? I'd like OP's code `this.router.navigate(['/']);` as the content of the error handler. Is this possible? – ZeferiniX Jun 24 '17 at 17:21
  • You can just use `subsribe` instead of `map`, or sou can use `catch` to handle errors. What kind of error do you expect? – Günter Zöchbauer Jun 27 '17 at 04:01
  • is it possible to get data updates in resolver? I mean after Observable.next() – Victor Bredihin Jul 22 '17 at 10:04
  • Not sure what the question is about. If you pass an observable from the resolver to the component, you can subscribe to in in the component and het a series of values. – Günter Zöchbauer Jul 22 '17 at 10:57
  • @GünterZöchbauer I receive an interface error when I try to use subscribe withh resolve: "Class incorrectly implements interface 'Resolve'." I see how I could use map and catch, but I've been using the new HttpClient module and the built in error handling there. How would you use subscribe instead of map here? – indigo Sep 01 '17 at 19:06
  • @A.Krueger I'd suggest to create a new question that includes your code. I can't tell from the error alone. – Günter Zöchbauer Sep 01 '17 at 19:44
  • Using .first() is only useful if you are only ever going to have a single result. It is often the case e.g. with BehaviorSubject, that the first mapped value will be an empty object/undefined, and you need to wait for future results – Drenai Feb 14 '18 at 10:43
  • Not sure what your point is. If you don't want the first event, then of course you shouldn't use `first()`. You can use for example `skip(1).first()` – Günter Zöchbauer Feb 14 '18 at 10:45
  • @GünterZöchbauer I'm just after figuring that I can use `.map.first()`, and add checks in my .map(), and only trigger `return result` when the correct result has been recieved. This works well, as it fires the complete(). I jumped the gun with with comment – Drenai Feb 14 '18 at 10:57
  • You can also use `.filter(...).first()` – Günter Zöchbauer Feb 14 '18 at 10:58
  • 1
    I was mistaken. If the map() gets called, then it triggers the complete(), regardless if I return a value or not. So the filter function will be needed. Thanks for the info – Drenai Feb 14 '18 at 11:36
  • Right. `map()` is just used to convert a value to a different value. This is why I suggested `filter()`. – Günter Zöchbauer Feb 14 '18 at 12:06