dev_054
dev_054

Reputation: 3718

Type 'Observable<Response | Observable<Response>>' is not assignable to type 'Observable<Response>'

I have a simple service with the following content:

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

import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class AddressService {

  constructor(private http: Http) { }

  getAnything = (): Observable<Response> => {
    return this.http.get('https://my_api.com')
      .map(this.handleSuccess)
      .catch(this.handleError);
  }

  handleError = (error: Response): Observable<Response> => {
    return Observable.throw(error || 'Server Error');
  }

  handleSuccess = (response: Response): Observable<Response> => {
    let body;

    if (response.text()) {
      body = response.json();
    }

    return body || {};
  }
}

It was working perfectly, until I upgrade Typescript from 2.3.4 to 2.4.1.

Now, after upgrade, I'm getting the weird error:

Type 'Observable<Response | Observable<Response>>' is not assignable to type 'Observable<Response>'

What's the point here? What are the changes in TS 2.4.x that make my app stop working properly?

Upvotes: 8

Views: 12626

Answers (3)

Jota.Toledo
Jota.Toledo

Reputation: 28434

Consider doing the following changes:

import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
import { _throw } from 'rxjs/observable/throw';

export interface Data{} // an interface to define the object in JSON format returned by your backend

@Injectable()
export class AddressService {

  constructor(private http: Http) { }

  getAnything = (): Observable<Data> => {
    return this.http.get('https://my_api.com')
      .map(this.handleSuccess)
      .catch(this.handleError);
  }

  handleError = (error: Response) : ErrorObservable { // The return type is ErrorObservable, which is a specialization of Observable<any>
    return _throw(error || 'Server Error');
  }

  handleSuccess = (response: Response) { // the return here is any, as you dont know how do the POJO(s) generated by response.json() look like
    let body;

    if (response.text()) {
      body = response.json();
    }

    return body || {};
  }
}

The newer versions of typescript introduced stronger type/generics detection, so that why the compiler is complaining about your current methods, as their return types just dont match.

In the latest release of angular, the http client was redone to avoid the parsing of untyped POJOs (calling r.json()). For this purpose the new http methods are generic, so developers can now map directly into a type when extracting the response content. For further information take a look at this link

Upvotes: 0

Peter Wone
Peter Wone

Reputation: 18765

The change is stronger type-checking.

An Observable<Response> is not assignable to something typed as Response because they are different types. For brevity lets call these types X and Y

Observable<X | Y> is not assignable to something typed as Y because while it might contain a Y it might also contain an X, and this is rejected by the stricter checking introduced in version 2.4.1

Capture the value of your expression into a let variable and return that variable. Then you will be able to inspect the type of the variable which will show you that it's not compatible just as the compiler reports.

To make your code compile you should test for an instance of the correct type before casting.

If you don't have time to clean up your codebase you can dial back the stronger type-checking with --noStrictGenericChecks

Here's some real TS code with a problem parallel to yours. In this example, FieldInstance lacks a Form property but FormInstance has one and this allows me to distinguish them and handle the type ambiguity. FieldInstanceRepeater is assignable to FieldInstance because it is a descendant.

constructor(scope: FormInstance | FieldInstanceRepeater, field: IFormFieldRow) {
  this.FormInstance = ((function getFormInstance(fi: FormInstance | FieldInstance): FormInstance | FieldInstance {
    let isFormInstance = !!(fi as FormInstance).Form;
    return isFormInstance ? fi : getFormInstance((fi as FieldInstance).Scope);
  })(scope) as FormInstance);
  this.Scope = scope;
  ...
}

Upvotes: 2

cartant
cartant

Reputation: 58400

TypeScript 2.4 introduced better checking for generics. It is highlighting errors in your code that should be fixed.

For example, the return type of handleSuccess does not match what it is returning; it's returning an anonymous object, but is typed as returning Observable<Response>. And because it's being used with map, you end up with a composed observable that's typed as Observable<Response | Observable<Response>>.

The errors you are seeing are real and should be fixed.

Upvotes: 7

Related Questions