Trying to understand this code: map((data: SearchResponse) => data && data.items || [])

Viewed 77

I am new to Angular and am trying to learn by examining code samples. I pulled some sample code for ngx-Typeahead and am trying to understand the code.

interface SearchResponse {
  total_count: number;
  incomplete_results: boolean;
  items: Owner[];
}

then

this.suggestions$ = new Observable((observer: Observer<string>) => {
      observer.next(this.search);
    }).pipe(
      switchMap((query: string) => {
        if (query) {
          return this.http.get<SearchResponse>('https://localhost:5001/api/GetOwners', {
            params: { q: query }
          }).pipe(
            map((data: SearchResponse) => data && data.items || []),
            tap(() => noop, err => { this.errorMessage = err && err.message || 'Something goes wrong'; })
          );
        }

        return of([]);
      })
    );

The line of code that says:

map((data: SearchResponse) => data && data.items || [])

Reads something like "map the response into this function and call it "data". Then with this, AND it with the items (an element of SearchResponse). If that AND is nothing, then return an empty array." So what if it isn't nothing? What does "data && data.items yield? Or am I overthinking this and data && data.items will return true so the whole data item is returned?

4 Answers

Using && and || like this is a shorthand for if/else to check a value or get a default.

data && data.items || [] is the same as if (data && data.items) data.items else [] (or (data && data.items) ? data.items : []. That is, if data is defined and data.items is not falsy (e.g. a non-empty array), you'll get data.items. Otherwise, you get an empty array.

This works because in Typescript, the && and || operators always return one of their arguments:

  • a && b is a if a is falsy, or b
  • a || b is a if a is truthy, or b

And because they short-circuit (only evaluate an operand if needed), you can use them to check a value before accessing it.

That means that if data.items exists and is non-empty, data && data.items will be that value. Otherwise, it will be a falsy value, and (that value) || [] will become the empty array --- a default.

This is a shorthand to ensure you're working with a properly defined value, in this case an array. When using an object as a condition like this, it behaves as if it's True if the object exists and False if it doesn't (i.e. null, undefined, 0, etc). If data is a non-null, defined thing, then the language processes the other side of the AND. If data.items is a non-null, defined thing then the expression short circuits. true && true || ??? doesn't need to process the 3rd element to know the expression evaluates as true. But the expression doesn't return true, it returns the last evaluated condition, so if data and data.items are both non-null, defined things, it'll return data.item.

What if data is a thing and data.item is not? Then we have true && false || ??? which can be shorted to false || ???. Short circuiting hasn't kicked in yet so we have to evaluate the third condition: an empty array. An empty array is a non-null, defined thing so it acts as true. It's also the last expression evaluated so the empty array is returned.

If neither data nor data.items hold a value, then the empty array is returned.

I don't believe it's possible for data to be null or undefined and for data.items not to throw an error.

In effect, all of this is equivalent to saying "I want data.items but if it's not defined, give me an empty array." It's fancy type coercion by taking advantage of the JS engine.

correct - assuming 'data' exists and data.items exist it will return the value at data.items (which I'm assuming is an array) - otherwise it returns an empty array.

I build a basic test to see how it work, map((data: SearchResponse) => data && data.items || []) after map data (truthy) and data,items (truthy) will pass { } in other cases will pass [] for the chaining.

// let data = {"items": null } // []

let data = {"items": {} } // {}

//data = null // []

// let data = undefined  // []
 
console.log( data && data.items || [])

Related