Subtle difference between map and pluck RxJS operators that you should know

Post Editor

Explanation in detail about how map and pluck work, and then figuring out main difference between two operators

3 min read
post

Subtle difference between map and pluck RxJS operators that you should know

Explanation in detail about how map and pluck work, and then figuring out main difference between two operators

post
post
3 min read
3 min read

Explanation in detail about how map and pluck work, and then figuring out main difference between two operators

Do you think the following code snippet will give the same result?

from(objectList).pipe(
  map(object => object.employee),
  map(employee => employee.address),
  map(address => address.houseNumber)
)
// vs
from(objectList).pipe(
  pluck('employee', 'address', 'houseNumber')
)

Well, the answer is not really the same. Let's take a closer look to see the subtle difference.

How map operator works

According to the official documentation, here's what map operator does

Applies a given project function to each value emitted by the source Observable, and emits the resulting values as an Observable.

But that's not the full picture. What will happen if an error occurs in the project function? When I deep dive into the implementation of map operator, here's what I figured out.

try {
  result = this.project.call(this.thisArg, value, this.count++);
} catch (err) {
  // if error occurs, map will emit an error notification and return
  this.destination.error(err); // <-- this line
  return;
}
What map operator does

You can see from the above implementation, if an error occurs in the project function, the map will emit an error notification and your output stream will hang on.

How pluck operator works

Here's what pluck operator does from the official docs

Maps each source value (an object) to its specified nested property.

When I read this line, there's a question popping out in my mind, what if the nested property does not exist in the object?

And here's the answer when I consult the source code of pluck operator

export function pluck<T, R>(...properties: string[]): OperatorFunction<T, R> {
  // if you pass pluck('employee', 'address', 'houseNumber')
  // the length will equal to 3
  const length = properties.length;
  ...
  // under the hood, pluck operator calls map operator,
  // and passes the plucker as projection function
  return (source: Observable<T>) => map(plucker(properties, length))(source as any);
}
How pluck operator does under the hood

As you can see, pluck operator calls map operator behind the scene, and passes the plucker as project function. Here’s what plucker does.

// if you call pluck('employee', 'address', 'houseNumber')
// props will be ['employee', 'address', 'houseNumber']
// and length will be 3
function plucker(props: string[], length: number): (x: string) => any {
  const mapper = (x: string) => {
    let currentProp = x;
    // loop through every passed properties in the list and get the nested value from object
    for (let i = 0; i < length; i++) {
      // if the object doesn't have the specified property, no error will be thrown...
      const p = currentProp != null ? currentProp[props[i]] : undefined; // <--this line
      if (p !== void 0) {
        currentProp = p;
      } else {
        // ...instead, it returns undefined
        return undefined; // <-- this line
      }
    }
    return currentProp;
  };

  return mapper;
}

So, from the above code, we can see that the pluck operator will get the nested value from an object based on the property list you provided. For example, you call pluck('employee', 'address', 'houseNumber') , it will try to get the value at object.employee.address.houseNumber, with the main difference being that it ensures null safety.

If there's no value inside a nested object, it will return undefined, and your stream will continue to the next emitted value, rather than throwing an error and stopping like a map operator does. This is the main difference between map and pluck operator.

Recap

Let me give you a concrete example to recap. Suppose I have the following input data

const arr = [
  {
    employee: {
      address: {
        houseNumber: 1
      }
    }
  },
  {
    employee: {
      // notice this employee doesn't have address
    }
  },
  {
    employee: {
      address: {
        houseNumber: 3
      }
    }
  },
];

const arr$ = interval(1000).pipe(
  map(index => arr[index]),
  take(3)
);

And I have two streams of data

const streamWithMap = arr$.pipe(
  map(object => object.employee),
  map(employee => employee.address),
  map(address => address.houseNumber)
);

const streamWithPluck = arr$.pipe(
  pluck('employee', 'address', 'houseNumber')
);

And here's the visualization of streamWithMap and streamWithPluck accordingly

streamWithMap
streamWithPluck

If I change the streamWithMap like this, the result will be the same as when I use pluck

const streamWithMap = arr$.pipe(
  map(object => object?.employee?.address?.houseNumber),
);

Conclusion

Throughout the article, I have explained in detail what's the main difference between map and pluck operators in RxJS by deep diving into the implementation. I also give an example and marble diagram to illustrate this difference.

I hope you learned something new from the blog. Thanks for reading.

Discuss with community

Share

About the author

author_image

Front end developer, Angular enthusiast

author_image

About the author

Hien Pham

Front end developer, Angular enthusiast

About the author

author_image

Front end developer, Angular enthusiast

NxAngularCli
NxAngularCli
NxAngularCli
Reviewed by
Max Koretskyi

Featured articles

Angularpost
4 March 20218 min read
Angular Universal: real app problems

Angular Universal is an open-source project that extends the functionality of @angular/platform-server. The project makes server-side rendering possible in Angular. This article will discuss the issues and possible solutions we encountered while developing a real application with Angular Universal.

Angularpost
4 March 20218 min read
Angular Universal: real app problems

Angular Universal is an open-source project that extends the functionality of @angular/platform-server. The project makes server-side rendering possible in Angular. This article will discuss the issues and possible solutions we encountered while developing a real application with Angular Universal.

Read more
AngularpostAngular Universal: real app problems

4 March 2021

8 min read

Angular Universal is an open-source project that extends the functionality of @angular/platform-server. The project makes server-side rendering possible in Angular. This article will discuss the issues and possible solutions we encountered while developing a real application with Angular Universal.

Read more
Angularpost
3 March 20215 min read
View State Selector  - Angular design pattern

As a web developer you may have noticed a repetitive boiler plate code of displaying a loader while an asynchronous request is being processed, then switching to the main view or displaying an error. Personally, I noticed these repetitions both in my code and other developers I work with. And even worse than the repetitive code is the fact that there are no indications for missing state views (such as unhandled errors or a missing loader). <div *ngIf="data$ | async as data"> <ng-container *ng

Angularpost
3 March 20215 min read
View State Selector  - Angular design pattern

As a web developer you may have noticed a repetitive boiler plate code of displaying a loader while an asynchronous request is being processed, then switching to the main view or displaying an error. Personally, I noticed these repetitions both in my code and other developers I work with. And even worse than the repetitive code is the fact that there are no indications for missing state views (such as unhandled errors or a missing loader). <div *ngIf="data$ | async as data"> <ng-container *ng

Read more
AngularpostView State Selector  - Angular design pattern

3 March 2021

5 min read

As a web developer you may have noticed a repetitive boiler plate code of displaying a loader while an asynchronous request is being processed, then switching to the main view or displaying an error. Personally, I noticed these repetitions both in my code and other developers I work with. And even worse than the repetitive code is the fact that there are no indications for missing state views (such as unhandled errors or a missing loader). <div *ngIf="data$ | async as data"> <ng-container *ng

Read more
RxJSpost
26 February 20213 min read
RxJS: Why memory leaks occur when using a Subject

It's not uncommon to see the words 'unsubscribe', 'memory leaks', 'subject' in the same phrase when reading upon RxJS-related materials. In this article, we're going to tackle this fact and by the end of it you should gain a better insight as to why memory leaks occur.

RxJSpost
26 February 20213 min read
RxJS: Why memory leaks occur when using a Subject

It's not uncommon to see the words 'unsubscribe', 'memory leaks', 'subject' in the same phrase when reading upon RxJS-related materials. In this article, we're going to tackle this fact and by the end of it you should gain a better insight as to why memory leaks occur.

Read more
RxJSpostRxJS: Why memory leaks occur when using a Subject

26 February 2021

3 min read

It's not uncommon to see the words 'unsubscribe', 'memory leaks', 'subject' in the same phrase when reading upon RxJS-related materials. In this article, we're going to tackle this fact and by the end of it you should gain a better insight as to why memory leaks occur.

Read more