Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correctly inferring types when chaining observables

I have an Angular2 app that has a service that fetches a list of items. There is a method to also fetch more specific information for each item in the list. My current setup is:

// service for item list
class ItemsService {
  fetchItems(): Observable<Item[]> {
    return this.http.get(url).map((res: Response) => res.json());
  }
  fetchItemsWithData(): Observable<Item[]> {
    return this.fetchItems()
    .flatMap((items: Observable<Item[]>) => items)
    .map((item: Item) => this.itemService.fetchData(item))
    .flatMap((items: Observable<Item[]>) => items)
    .toArray();
  }
}

// service for individual items
class ItemService {
  fetchData(item: Item) {
    return this.http.get(`${url}/${item.id}`)
    .map((res: Response) => res.json());
  }
}

This actually works exactly as I need it to even though the array <=> observable seems a little funky, but it does end up giving me an observable of an array of items with all of the item information.

The problem is, on the return this.fetchItems() line I always get:

The type argument for type parameter 'T' cannot be inferred from the usage.
Consider specifying the type arguments explicitly.
  Type argument candidate 'Item[]' is not a valid type argument because it is not a
  supertype of candidate 'Observable<Item[]>'.
    Property 'length' is missing in type 'Observable<Item[]>'.

I'm at a bit of a loss since fetchItems does return an Observable and changing it to Observable<any> gives the same error. If I just change it to any though I don't get any error reports at all.

Is there a better type to use for fetchItems?

like image 203
Explosion Pills Avatar asked Oct 25 '16 21:10

Explosion Pills


2 Answers

When I ran into same issue, the reason was that I forgot to import Response class:

import { Http, Response } from '@angular/http';

Hope it helps someone.

like image 110
yefrem Avatar answered Nov 12 '22 04:11

yefrem


I'm suspicious this might have something to do with toArray() which returns Observable<T[]>.

In your case where you define fetchItems(): Observable<Item[]> it makes toArray() to return Observable<Observable<Item[]>[]> so that's why it throws an error even with Observable<any>.

Btw I tried to simulate your situation with just mock classes and it compiled without errors (TypeScript 2.0.3) but maybe I made an error somewhere and its not identical (?):

class Observable<T> {
    map(_): Observable<T> { return new Observable<T>() }
    flatMap(_): Observable<T> { return new Observable<T>() }
    toArray(): Observable<T[]> { return new Observable<T[]>() }
}
class Item { }
class Response {
    json() { }
}
class HTTP {
    get(_) { return new Observable() }
}


class ItemsService {
    itemService = new ItemService();
    http = new HTTP();

    fetchItems(): Observable<Item[]> {
        return this.http.get('url').map((res: Response) => res.json());
    }

    fetchItemsWithData(): Observable<Item[]> {
        return this.fetchItems()
            .flatMap((items: Observable<Item[]>) => items)
            .map((item: Item) => this.itemService.fetchData(item))
            .flatMap((items: Observable<Item[]>) => items)
            .toArray();
    }
}

// service for individual items
class ItemService {
    http = new HTTP();

    fetchData(item: Item) {
        return this.http.get('url')
            .map((res: Response) => res.json());
    }
}
like image 39
martin Avatar answered Nov 12 '22 04:11

martin