1

This is my transactions API response:

[{
   "id": 1,
   "description": "Sandwich",
   "date": "2017-09-01",
   "category": "Take away",
   "tags": ["Holidays"],
   "amount": -2
},{
   "id": 2,
   "description": "Wage",
   "date": "2017-08-31",
   "category": "Wage",
   "tags": [],
   "amount": 2000
}]

I have the following code in my Angular service:

@Injectable()
export class TransactionsService {
    private _transactionssurl = 'app/transactions/transactions.json';

    constructor(private _http: Http){}

    getTransactions(query = {}): Observable<ITransaction[]> {
        return this._http.get(this._transactionssurl)
            .map((response: Response) => <ITransaction[]> response.json())
            .filter((value: any) => {
                console.log(value)
                if (query["month"] && transaction["month"].indexOf(query["month"]) === 0 ) return false;
                if (query["category"] && transaction["category"] !== query["category"]) return false;
                if (query["tag"] && transaction["tags"].includes(query["tag"])) return false;
                return true;
            })
            .do(data => console.log(data))
            .catch(this.handleError);
    }

    //...

}

You can see above that I'm trying to filter the data based on the passed in query object e.g. {month: "2017-09", tag: "Holidays"} However, it seems that the function argument - transaction - which is passed into the filter function is the whole transactions array, not just a single transaction object which is how I expected it to be - how should I arrange the code to filter each individually? What should I be doing here instead? Most of the examples I've seen look somewhat like this so I tried to copy them.

2
  • You're calling Observable.filter, which filters the stream of events, not Array.filter. Commented Sep 3, 2017 at 19:55
  • You're trying to filter the observable (i.e. the events emitted by the observable), instead of filtering the elements of the array emitted by the observable. The filtering should be inside the callback function passed to map(), after you've extracted the array from the response. Commented Sep 3, 2017 at 19:57

1 Answer 1

2

your observable stream that the operators act on is the ENTIRE response, which in this case is a list of data, not the elements in that list. You just need to act accordingly based on your desired outcome... if you want each item in the list to be transformed into items in an observable stream, so they go through operators one by one to the ultimate consumer then you do this:

getTransactions(query = {}): Observable<ITransaction[]> {
    return this._http.get(this._transactionssurl)
        .map((response: Response) => <ITransaction[]> response.json())
        .flatMap(data => Observable.from(data))
        .filter((value: any) => {
            console.log(value)
            if (query["month"] && transaction["month"].indexOf(query["month"]) === 0 ) return false;
            if (query["category"] && transaction["category"] !== query["category"]) return false;
            if (query["tag"] && transaction["tags"].includes(query["tag"])) return false;
            return true;
        })
        .do(data => console.log(data))
        .catch(this.handleError);
}

The flatMap -> from operation takes your list data and flattens it into an observable stream. But this doesn't make sense if you actually want the entire list at your final consumer in one observable stream item, as you would have to re aggregate the response with a reduce operator at the end like:

 .reduce((acc, val) => acc.concat([val]), [])

If you just want to filter the list and pass it along, it's simple:

getTransactions(query = {}): Observable<ITransaction[]> {
    return this._http.get(this._transactionssurl)
        .map((response: Response) => <ITransaction[]> response.json())
        .map((list: any) => {
            return list.filter(value => {
                if (query["month"] && transaction["month"].indexOf(query["month"]) === 0 ) return false;
                if (query["category"] && transaction["category"] !== query["category"]) return false;
                if (query["tag"] && transaction["tags"].includes(query["tag"])) return false;
                return true;
            });
        })
        .do(data => console.log(data))
        .catch(this.handleError);
}

this just expects an array of data in the stream and runs the normal array filter method on it and maps it into the filtered list.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the very detailed response. I tried another approach from inside the map call instead chaining filter() to that, so response.json().filter(...) which I guess is similar? I see you've chained another map() call though which looks somewhat neater.
My preference is to have each operator do as little as possible individually. I think the code is cleaner and easier to understand and maintain that way when you come back in a few months and forget why you did what you did

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.