Build Your Own Observable Part 1: Arrays
In this article we will learn how build Your Own Observable Part 1: Arrays.

Build Your Own Observable Part 1: Arrays
In this article we will learn how build Your Own Observable Part 1: Arrays.


Angular uses RxJS observables. I can’t understand something until I create it with my own hands. Perhaps you are the same. Either way, let’s build an observable from scratch!
When I first started working with RxJS, I didn’t know what I was doing. Now, I’m able to solve problems using observables, and I can even read the RxJS sources with confidence. It’s definitely something you can do too.
The only catch is, to understand observables, you have to first understand arrays.
ArraysLink to this section
We’ll see pretty shortly that observables are basically the same mental model as arrays, just with an added dimension of time. But, that’s not going to do you any good if you don’t already feel comfortable with arrays.
NOTE: If you are already very comfortable with map, filter, reduce, and flattening nested arrays, you can skip this article and go to part 2.
StartLink to this section
Here’s a task for you. Create an array in JavaScript that contains the numbers 1
, 2
, and 3
.
<>Copyconst arr = [1,2,3];
Now, go ahead and write some code that adds one to each of those values, and returns the results in a new array.
<>Copyconst arr = [1,2,3]; const mappedArr = arr.map(value => value + 1); // returns [2,3,4]
Perfect. Array.prototype.map
is the magic that lets us do this. It takes a projection function, in this case vale => value + 1
and applies it to every value in the array, then returns a new array. You could implement it by hand, if you really wanted to:
<>Copy// overriding properties on Array.prototype is a bad idea. This is given for educational purposes only :D Array.prototype.map = function(projectionFn) { let retVal = []; for (let i = 0; i < this.length; i++) { retVal.push(projectionFn(this[i])); } return retVal; }
Nothing magic about that. We simply loop over the elements in the array, and apply the projectionFn to each element, and push the results of that into a new array. We use this
here to reference the array, since we are calling map
off of Array.prototype. [1,2,3].map(v => v + 1)
Let’s cover filter
next. Maybe now you want to filter the array to return only even numbers:
<>Copyconst arr = [1,2,3]; const filteredArr = arr.filter(v => v % 2 === 0); // [2]
filter
takes a predicate function, in this case v => v % 2 === 0
, and applies that to every element in the array. Elements which return true
when passed into the predicate will be added to the returned array. Let’s implement filter as well:
<>CopyArray.prototype.filter = function(predicateFn) { let retVal = []; for (let i = 0; i < this.length; i++) { if (predicateFn(this[i]) { retVal.push(this[i]); } } return retVal; }
Again, very simple implementation.
The goal here is to convince yourself that nothing magical is happening with these Array.prototype functions. They aren’t some blackbox — you could write them yourself if you wanted to.
By familiarizing yourself with their implementations, it will be that much easier to implement the same functions on observables later.
Now that we have both map and filter, we can do some sweet composition!
<>Copyconst arr = [1,2,3,4,5,6]; const composedArr = arr.map(x => x + 1).filter(y => y % 2 === 0); // [2,4,6];
Notice how our map
and filter
methods always return arrays. This is a very important point for composing array operations. If they returned something other than an array, like undefined
or null
or 567
then we would no longer have access to Array.prototype
, and therefore would not be able to compose any further.
For example,[1,2,3].map(x => x + 1).567.filter(x => x > 2)
doesn’t end well, since 567
doesn’t have a filter
method (since it isn’t an array, and that’s what we are trying to compose).
Composition is a fascinating topic. MPJ has a nice series on Youtube which you can find here, and Eric Elliott has a great series on the topic as well.
As we’ll see later, observables are composable as well.
You’ve read this far and we’re still talking about arrays? I promise we’ll get to observables soon enough, but there’s one more observation we need to take from arrays. Once you have that, observables will be trivial.
What does this code return?
<>Copyconst gameData = [ { title: 'Mega Man 2', bosses: [ { name: 'Bubble Man', weapon: 'Bubble Beam' }, { name: 'Metal Man', weapon: 'Metal Blade' }] }, { title: 'Mega Man 3', bosses: [ { name: 'Gemini Man', weapon: 'Gemini Laser' }, { name: 'Top Man', weapon: 'Top Spin' }] } ]; // return an array of all bosses const bossesArray = gameData.map(game => { return game.bosses; }); // uh oh, those are nested arrays! // [[{},{}],[{},{}]]
On line 30
, we map
over the gameData
array. map
always returns an array. Always.
However, inside of the call to map
, we return game.bosses
, which is also an array, so we end up with a nested array. [[{ …boss data…}]]
.
This is probably not what we wanted. Ideally, we’d like to have everything flattened into a single array. We simply need a method which will take an array, and reduce its depth by a factor of one. Since we have a single layer of nesting in this array, we would want to apply this depth-reducing method once.
Unfortunately, Array.prototype
does not currently have a good way of doing this. Array.prototype.concat
can be used, although it is not ideal for this situation. In the future, we’ll have access to flat and flatMap, but that doesn’t do us much good for now, so let’s implement our own flatten
function. It will return a new array with depth reduced by a factor of one.
<>CopyArray.prototype.flatten = function() { let retVal = []; this.forEach(a => { retVal = retVal.concat(a); }); return retVal; } let arr = [[1,2], [3], 4, [5,6], [[7], 8]]; console.log(arr.flatten()); // [1, 2, 3, 4, 5, 6, [7], 8]
Now, we can go back to our example from above, and apply flatten
to the nested game.bosses
array returned from map
:
<>Copyconst bossesArray = gameData.map(game => { return game.bosses; }).flatten(); // returns a flattened array of boss objects [{}, {}, {}, {}]
A map
followed by a flatten
is such a common set of operations, that most languages combine the two into a flatMap
operator.
<>CopyArray.prototype.flatMap = function(fn) { return this.map(fn).flatten(); } // usage const bossesArray = gameData.flatMap(game => { return game.bosses; }); // [{}, {}, {}, {}]
Being able to delve down a layer with map
, and then come back up a layer with flatten
is a very useful skill to have when working with nested data structures, such as arrays. This comes up on a regular basis when working with higher-order observables as well (observables of observables). If you’ve ever found yourself dealing with nested observables, and reaching for the flatMap
operator, then you’ll know what I mean.
That’s it for part one. If you’re feeling comfortable with all of these array methods, then it’s time to make everything asynchronous with observables.
SummaryLink to this section
- Array methods like
map
andfilter
are great for composition - Those methods aren’t mythical creatures — you could easily write them yourself
- When you have nested arrays, you need something more sophisticated, like a
flatMap
. - As we’ll see, these concepts carry over to observables nicely.
LinksLink to this section
Part 2: Containers and Intuition
Comments (0)
Be the first to leave a comment
About the author

Fullstack Developer. Love digging into the internals of stuff. Always trying to reach the next level.

About the author
Nate Lapinski
Fullstack Developer. Love digging into the internals of stuff. Always trying to reach the next level.
About the author

Fullstack Developer. Love digging into the internals of stuff. Always trying to reach the next level.