4

I have an Angular 4 Component that is calling a Service to get data. Not the strangest of situations. After I retrieve the data and need to transform it and filter it. Apparently the way to do this nowadays is to use pipe.

In my Component:

ngOnInit(): void {
    this.suService.getShippingUnitTypes().subscribe(res => {
        console.log("Getting shipping unit types: ", res);
    });
}

In my service:

getShippingUnitTypes(): any {
    const convertToJson = map(value => (value as any).json().XXETA_GRID_STATIC_LOV);
    const filterShippingUnit = filter(value => (value as any).LOV_TYPE == "SHIPPING_UNIT");

    return this.http.get(
        this.constantsService.LOOKUP_COLUMN_BATCH_URL
    ).pipe(convertToJson, filterShippingUnit);
}

The service imports the following:

import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers, RequestMethod } from '@angular/http';
import { Observable, pipe } from 'rxjs/Rx';
import { map, filter } from 'rxjs/operators';

When debugging, the code never errors but simply never gets to the console.log() statement in the Component. If I remove the .pipe() and simply return the Observable the code logs what I would expect only without the transforming and filtering.

I'm very new to Rxjs and using Pipe. I'm obviously not understanding something.

Edited to Add Information:

I put tap into the pipe like such...

pipe(tap(console.log), convertToJson, tap(console.log), filterShippingUnit, tap(console.log))

I didn't know that tap existed but it is useful. The first two console logs give me what I would expect. The third one, right after the filterShippingUnit, doesn't do anything. It doesn't log a value at all. Not even null.

After convertToJson console.log spits out an array of 28 objects. One of the objects is:

{LOV_TYPE: "SHIPPING_UNIT", LOV_TAB_TYP_ITEM: Array(4)}

I would expect that object to be passed on based on the filterShippingUnit filter.

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
Andrew Cooper
  • 723
  • 2
  • 14
  • 28
  • 1
    (value as any).json() why are you doing that? – Jota.Toledo Dec 19 '17 at 15:15
  • 1
    Are you sure the response doesn't get filtered out? You can use `tap` to see what's going on inside the chain `.pipe(tap(console.log), convertToJson, tap(console.log), filterShippingUnit);` – martin Dec 19 '17 at 15:20
  • Yeah, move that console command up the chain until it's called. – Joe Dec 19 '17 at 15:23
  • @Jota.Toledo Because Typescript gives an error that value doesn't have the json() method. Value is type object. I happen to know that Value has a json() method so I'm telling Typescript to ignore typing for this statement. – Andrew Cooper Dec 19 '17 at 15:25
  • 1
    And why do you think is a good idea to ignore the types of the variables you are using, when you indeed know them? on the first function, you know that value is a Response object, so you should declare it explicitly instead of tricking the compiler to think its something different. – Jota.Toledo Dec 19 '17 at 15:47
  • 1
    @Jota.Toledo Not knowing much about this stuff yet, I got my information from https://blog.hackages.io/rxjs-5-5-piping-all-the-things-9d469d1b3f44. The examples do it exactly like I've done here. It may not be best practice. I don't know. After it works I'll worry about best practice. – Andrew Cooper Dec 19 '17 at 15:56
  • @msanford I know what an Angular pipe is. This is pipe() from Rxjs which seems to be standard for Angular 4 development now. Since the name of the function is "pipe" I'm not sure what else to call it. I inherited an Angular 4 project so I'm getting a bit of a crash course in Typescript, React, and other things I didn't need before. – Andrew Cooper Dec 19 '17 at 16:30
  • @AndrewCooper Cool. TIL though, as this is literally the first time I've ever seen it used this way. However, knowing Angular, this 'new standard' may be quite new. (Yup, since October, [rx 5.5](https://github.com/ReactiveX/rxjs/blob/master/CHANGELOG.md#550-2017-10-18)) – msanford Dec 19 '17 at 16:33

1 Answers1

4

The problem is most likely here:

const filterShippingUnit = filter(value => (value as any).LOV_TYPE == "SHIPPING_UNIT");

Assuming that after parsing the body of the response to JSON, you get an array of type Foo, where Foo is defined as follows:

interface Foo {
 LOV_TYPE: string;
 fooProperty: string;
 fooNumber: number;
}

You are trying to apply the filter to the array object, not to the objects contained in it.

You have two options: flatten the array and emit its values as singular events, and then put them together again as an array, or map the array to a new one; the second one being the easiest as follows:

const filterShippingUnit = map((list: Foo[])=> list
              .filter(foo => foo.LOV_TYPE === "SHIPPING_UNIT"));

The first approach could be implemented as:

import { flatMap, toArray } from 'rxjs/operators';

return this.http.get(this.constantsService.LOOKUP_COLUMN_BATCH_URL)
    .pipe(
      flatMap(response => response.json() as Foo[])
      map(foo => foo.LOV_TYPE === "SHIPPING_UNIT") // TypeScript will infer that foo is of type Foo
      toArray
     );

As its easy to notice that you are just starting in angular, I would suggest you the following:

  • Define interfaces for everything that comes from a backend
  • Use the new HttpClient API from angular (Http is deprecated, see https://angular.io/guide/http)
  • I dont think is necessary to define constant functions to store operations that you will use in a stream (as suggested in the tutorial/guide you are following). You lose all the type information by doing that, if you dont declare the argument types explicitly. But dont trust me on this one, some people say that despite the fact that typescript can infer a type, its a good practice to declare it explicitly...
Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
  • This is what I was missing. I agree with you on declaring the operations as functions. I've since removed them. I'm very familiar with Angular, at least previous versions, but this whole move to Typescript and Rxjs is really throwing me. The learning curve is almost as steep as learning a whole new framework. Thanks for the help. – Andrew Cooper Dec 19 '17 at 16:55
  • Im having the same issue, my map doesn get called, any idea why? I need more details on this problem – John Jun 14 '18 at 05:25
  • @Juan feel free create a new question and describe your issue. – Jota.Toledo Jun 14 '18 at 05:54
  • I did....the solution was to use catchError function wrapped with a pipe() for rxjs6. If the connection failed or an error happened on the server rxjs 6 bypasses the map function and go straight to the catchError().....example: pipe(map(data=>{ console.log("Only comes here if response is 200"); }),catchError(err=>{ console.log("Comes here if something fails, never passes through map if there is an error");}) – John Jun 18 '18 at 17:37