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
1 comment
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
1 comment
1 comment

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

<>Copy
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
Link to this section

According to the RxJS reference, 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.

<>Copy
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
Link to this section

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

<>Copy
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.

<>Copy
// 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
Link to this section

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

<>Copy
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

<>Copy
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

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

Conclusion
Link to this section

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.

Comments (1)

authorStackUndertow
19 August 2021

Hot Sports Opinion: Pluck needs to be scrubbed from the library, or have the T,R generics removed. It's in the class of functions that "pretends really hard" about having types and is therefore unpredictable.

authormaxkoretskyi
26 August 2021

not everyone needs typing

Share

About the author

author_image

Front-end developer at Inspectorio, Angular enthusiast

author_image

About the author

Hien Pham

Front-end developer at Inspectorio, Angular enthusiast

About the author

author_image

Front-end developer at Inspectorio, Angular enthusiast

Reviewed by
Max Koretskyi
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
AngularpostTracking user interaction area

13 September 2021

8 min read

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
Angularpost
7 September 202122 min read
Designing Angular architecture - Container-Presentation pattern

Designing architecture could be tricky, especially in the agile world, where requirement changes are frequent. So your design has to support that and provides extendibility without the need for serious modification. In such cases, you will find the Container-Presentation pattern instrumental.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more
micro frontendspostTaking micro-frontends to the next level

6 September 2021

25 min read

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more