Search code examples
angulartypescriptangular6angular-httpclient

TS 2322 in some HttpClient services after upgrading angular 5.2 to 6.0.7


I'm mid-way through an update to Angular 6 with TypeScript ~2.7.0. In Angular 5.2.12, I wrote service methods like this:

isPartDraft = (part: number): Observable<boolean> =>
  this._http.get(`${this.rest}/isDraft/${part}`)
            .catch(MyPartService.handleError);

annotating the function definition with a return type, but without asserting its type within the function body.

It seems that is no longer possible in some contexts, as tsc raises:

TS2322:
Type 'Observable<Object>' is not assignable to type 'Observable<boolean>'.
  Type 'Object' is not assignable to type 'boolean'.

and I'm forced to assert the return type within the function body as recommended in the current Angular documentation:

isPartDraft = (part: number) =>
  this._http.get<boolean>(`${this.rest}/isDraft/${part}`)
      .pipe(catchError(MyService.handleError));

So far so good (we never changed our annotations since angular2-rc6, so this might have been lurking for a while).

But in these cases I don't get any TS error:

getPasswordPolicy(): Observable<PasswordPolicy> {
  return this._http.get(`${this.rest}/securityPolicy/`)
    .pipe(
      map((securityPolicy: any) => securityPolicy.passwordPolicy),
      catchError(error => observableThrowError(error))
    );
}

And in a different service:

getPartsAtDate = (id: number, date: string, time: string): Observable<number[]> =>
  this._http.get(`${this.rest}/partsOnDate/${id}/${date}/${time}`)
    .pipe(catchError(MyService.handleError));

getAllNewOrders = (id: number): Observable<Orders[]> =>
  this._http.get(`${this.rest}/${id}`)
    .pipe(catchError(MyService.handleError));

So how do the pipe(), catchError() and map() operators interact to produce this situation? Why can't I annotate the function definition in the first case but can in the second three?

(Of course I could robotically fix the places tsc throws errors, but I'd much rather understand what's going on, especially as identical contexts seem to produce different results.)

As requested, here is the error handler (the implementation is identical in each service):

private static handleError(error: any) {
  console.log('error ' + error);
  return observableThrowError(error.status || 'Server error');
}

Solution

  • The method get is overloaded and not all versions use Generics. Based on how you call get in your first example the signature that is chosen is the one that returns Observable<Object>. If you want another kind of observable you have to use generics (like get<boolean>).

    Why do the other examples work?

    map((securityPolicy: any) => securityPolicy.passwordPolicy),
    

    Here, you explicitly change the type from Observable<Object> to Observable< PasswordPolicy>. It wouldn't work if you had map((securityPolicy: SecurityPolicy.

    catchError doesn't change the type of the observable like map does in your case. So it's still Observable<Object>.