Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing return type of an Observable with map

I've been researching how to change the return type of an Observable.

I'm using Angular 5.

Here's an example :

public getButterfly(): Observable<Butterfly[]> {
    return http.get<Larva[]>('url').pipe(
        map( larva => new Butterfly(larva.dna))
    );
}

This code cause an ERROR because for the compiler expect the larva object to be a Butterfly because of the return value and throw an error:

"error TS2339: Property 'id' does not exist on type 'Butterfly[]'."

It seems that typescript doesn't allow type changes inside the observable, but if you know a way, i'm all ears.

Thank you for taking an interest in my question.

like image 933
pierre Avatar asked Apr 19 '18 08:04

pierre


People also ask

Does map return an observable?

get(apiURL) returns an Observable . map is an observable operator which calls a function for each item on its input stream and pushes the result of the function to its output stream. In this case each input item is a Response object.

What does map return RXJS?

The map() operator's return value is observable that emits the values from the source Observable transformed by the given project function.

What is return type of map in Angular?

Using Map() method in TypeScript Angular, returning array of Observable in a function.

What is the use of map in observable?

map is part of the so-called transformation operators group because it's used to transform each item received from the source observable. The operator passes each source value through a projection function to get corresponding output value and emits it to an observer.


1 Answers

I think the accepted answer has masked a bigger problem, and perhaps a confusion in your understanding of what map actually does in rxjs.

In 'normal' Javascript - the map() function operates directly on an array and runs a function for every item in the array. eg. [1,2,3].map(x => x + 1). You immediately get back a full array of the same length as the original - with the transformed items.

In rxjs it is a completely different function that's part of rxjs and only has the same name as Array's map (go find the sourcecode if you dare!).

The actual function you pass to the rxjs map() still takes a single value and returns a single value (so it looks very much like the pure javascript map) but instead of operating on an array it operates on a single value in a stream.

Ok so you already know RXJS is about streams! And you have a stream of larvae right! (I don't know if this is terrifying or adorable!)

Well you actually don't. With http.get you do have a stream, but it only contains ONE value and that ONE value is the entire response from your http call whatever it may be.

By the look of it you're returning an array of Larva objects from the get call, but as far as RXJS is concerned this is a stream of ONLY ONE item.

This is your original code (annotated):

  public getButterfly(): Observable<Butterfly[]> {
   return http.get<Larva[]>('url').pipe(
       map( larva => {

           // We are now inside the RXJS 'map' function, NOT a Javascript map
           // here larva is an ARRAY (assuming your HTTP call actually returns an array)
           // So if you execute larva.dna you'll get 'undefined' 
           // (because dna is not a property on a javascript array!)
           // So you will return a butterfly OBJECT, but initialized with 'undefined' DNA. Scary!

           return new Butterfly(larva.dna);
       })
   );
}

So what I think you actually want is this:

public getButterflies() {
    return http.get<Larva[]>('url').pipe(
        map( larvae => larvae.map(larva => new Butterfly(larva.dna)))
    );
}

If that doesn't immediately make sense here's what's happening:

  • You get a array back from the http call (larvae - which you're telling typescript is typed as Larva[])
  • We then run the standard javascript map function on that array for EACH item and create a butterfly for each. You then return that new butterfly array as a replacement item in the stream.
  • Remember you have a stream. But it is a stream of one item. And each item is an array.
  • Note: The output type doesn't need to be specified and is automatically Butterfly[]

By sticking in <Larva, Butterfly> you're just telling the compiler that's what it is so you don't get compile time errors. You're never changing anything.

PS. Sometimes I like to specify output type, when it's simple - in order to show mistakes inside the 'pipe'. I'll do this if I have several code paths, each of which must return the same. By constraining the output type it reveals more errors.

Tip: Use tap(x => console.log('Message', x) in the pipe to write to the console what you have at each stage like this:

public getButterflies() {
    return http.get<Larva[]>('url').pipe(
        tap( x => console.log('Before map', x) ),
        map( larvae => larvae.map(larva => new Butterfly(larva.dna)),
        tap( x => console.log('After map', x) )
    ));
}

Why is it called tap? Because pipes have taps! And a (regular water) tap lets you see what's inside a (regular water) pipe :-)

like image 196
Simon_Weaver Avatar answered Oct 15 '22 02:10

Simon_Weaver