Exploring RxJs Operators - map

In a past post I talked about the filter operator in RxJs, perhaps there is an argument for having talked about map before that, why? Simply put; you are going to use it a lot!

Now, before talking about map I want to make clear that if you are comfortable with the functional paradigm then you probably don't need to read any further, safe in the knowledge that you already know what you need to know about the map operator.

This leads us on nicely to talking about what map is, simply it is a way of transforming one value to another value, therefore it is safe to call this operator, the transformation operator.

Here is an example using the map operator:

1| private handleEnterPressed = this.keyboardEvents.pipe(
2|     filter(this.forEnterKey),
3|     map((val) => val.key),
4|     tap((val) => console.log(val + ' pressed))
5| );



First off, we filter for only the Enter KeyboardEvent on line 2.

Line 3 is what we are really interested in, we are passing in val at line 3 which is of type KeyboardEvent and then we are returning the key property, from this point on in our pipe we will receive the value of key only and as we know we filter for Enter earlier on in the pipe we know the value returned will always be 'Enter'.

The point of map is to ensure that future operators receive data that is suitable for that operator in both ensuring the data is in the correct format but also to ensure future operators can deal with data simply, this should raise a question around the example above; clearly, tap receives a value which is used as part of a print out but it probably makes sense to handle the concatenation of the two values to be done prior to the handling of printing out to console (this follows good practice of ensuring you isolate side effects as much as possible).

The below example shows how we can achieve this with an additional map operation, I believe it is always best practice to ensure that an operator receives data in a suitable state and that map has the sole responsibility of performing transformations on events.

1| private handleEnterPressed = this.keyboardEvents.pipe(
2|     filter(this.forEnterKey),
3|     map((val) => val.key),
4|     map((val) => val + ' pressed'),
5|     tap((val) => console.log(val))
6| );

A very simple example that is not very useful but I am sure many a reader will be thinking of a plethora of situations where the map operator will be very useful.

Finally as was the case with the filter operator I think it is useful to pull transformation logic out of the map operator into its own pure function:

1| private fromKeyboardEventToKey = (keyboardEvent) => (keyboardEvent.key);
2| private fromKeyToEnterPressedMessage = (key) => (key + ' pressed);
3| private handleEnterPressed = this.keyboardEvents.pipe(
4|     filter(this.forEnterKey),
5|     map(this.fromKeyboardEventToKey),
6|     map(this.fromKeyToEnterPressedMessage),
7|     tap((val) => console.log(val))
8| );

My reasoning for this is much the same as my reasoning for doing this with filter, the pipe is very expressive in what it is doing and whilst I acknowledge that well-named functions do not always do what they suggest I also believe that this makes testing of these functions much easier, I know many would see the reusability of such functions as the primary benefit but, honestly, for me it is all about ensuring pipes remain declarative, readable and expressive to those reading them.

A final thought on testing if you favor a composite of many map functions over one gargantuan mapping function then this will mean that your testing will be easier, resulting in higher test coverage and meaning that it is much quicker to isolate issues, you should avoid relying on testing just the pipes overall functionality and instead place emphasis on testing your pure functions that are used within the pipes, I appreciate this final thought somewhat goes outside the scope of pipes and the map operator but I feel it is useful to keep in mind when deciding on where to put your mapping logic.

Once again we find ourselves with a powerful operator which is extremely simple, it handles one thing well - transformation within pipes, in future posts we will explore more operators within RxJs as well as exploring potential usages of a composite of operators.





Comments

Popular posts from this blog

Angular material table with angular 7 - Defining columns using ngFor

Creating contextual drop downs using RxJs, Angular 7 and Angular Material

From nothing to an Angular 7 app running in 5 minutes with AWS