Tracy Lee is a serial entrepreneur and Google Developer Expert who loves to build companies and communities. She is the co-founder of This Dot Media and This Dot Labs. Her most recent love is coding in Angular, React, and Ember.In her free time, she runs Venture Hacked with the mission to help create relationships between startups and investors.After selling her company, Dishcrawl to DinnerLab in Dec 2015, Tracy has been advising and mentoring startups on sales, marketing, and growth. During her tenure at Dishcrawl, she helped scale the company into 250 cities with over 180 people across the world.View the profile
Ben Lesh is the technical lead for RxJS, an independent open source project. Ben got his start in web development more than 20 years ago, working as a contractor in Ohio, and later on working in Pennsylvania, before moving to California to work for Netflix. About 2 years ago, Ben joined Google as Software Engineer and is now working on the Angular team.View the profile
About the talk
Once you get past the basics of piping operators with RxJSl, you’re ready to unleash the full power of reactive programming! It’s time to start thinking about building your own operators. Ben will demonstrate solving common problems such as composing existing operators, are guide developers on how to think about and define your own operators.
Follow us on twitter https://twitter.com/ngconf
Official Website: https://www.ng-conf.org/
Consulting come find me. If you want to chat about that today. I'm going to give a quick update on what's happening in the arcs yet Community is so many exciting things happening. Right first off. We are so proud to see the progress of the library. We are currently at 8.7 million mpm downloads per week. That is crazy. graduation anyways at speaking of that. 6.5. One was actually just released and there's a few bug fixes and new releases. You might have noticed new features. As one of the
things that we were really excited to release was from fish from Fish. That is a wrap the native. So if you use tomato Fest before you'll know that it's not organic to cancel. The abort controller is it gives of cancellation fee subscriptions using a bore controller under the hood, by the way, if you didn't know such an important role are also Now supported in all major browser. Another word for storing objects dictionaries of observable to get back a stream of objects with the same shape easier to get values by
name from the observable that you are joining. We actually have the same changes plan for Combined latest and zip that you can look forward to We also added scheduled and I know Michael fog is very excited about this when he was talking about it. The other day is a replacement for the signature of that accepts a scheduler stuff before you might call from with an array and passive scheduler. But now that you can actually do if you can use scheduled a ray and have the scheduler and this one is very important because it actually prepares us to refactor Arts Chad. So excited about that to
be smaller and faster in upcoming releases speaking of that if you're ever interested in our chests 7 kind of what's coming up. You can just ask this is Billy right over here and that will give you the Arts gs-7 roadmap. I'll go through a few of the different things that we should be looking forward to going to be a lot of huge changes. This is my step toward arcgis 8, which is a me again to be smaller and faster, but we'll be doing is we'll be updating the library to texture 3 to take advantage of some of the new features and improve typing. What are the beads to
fabric eating some apis to make the service area smaller without impacting what you can actually do with rxjs. We also have a 10 to add a lot of size system of operator. Operators got to lose some weight if you're excited about our own one and I was coming off you can access to design dock at this Bentley right over here. We can look forward to hear is this is going to make the library a lot smaller. So there's going to be about 60% smaller footprint in the new angular CLI ask According to some of the informal testing about the team has done and it'll help to have a very much more concise
API notice with transitions from 6 to 7 is that we're really trying to engineer everything to make upgrading as smooth as possible for you. Last update is rxjs.. I love that. Our chance is always hot pink not hot pink. It should be I love that. We should have warned hot pink events on the documentation. Thanks to the greater Arts Jazz team The Core theme has been Contributions of an awesome ocean Trayvon. I like to call it the choose-your-own-adventure about what this
does is if the oldest ask you a series of questions and help you find the operative that you're looking for their very very useful contribution. So if you go to the doctor, I'll be a little pencil icon on every single page and if there needs to be any updates, you can just click on that pencil it'll take you directly to the GitHub repo and you can create a PR really Isley exciting right it is okay. So I mean again the gross and the community that we seen in the past year or so has been so amazing come contribute. You can also DM myself or ban on Twitter
anytime and we'd be happy to get you started. Are you ready? Then? I'm leaving you now. Thank you Tracy for giving us all the details and what the upcoming Tracy been Fantastic Four the team. All right, so good about how to build your own operators first off my team. I'm also the RCs lead I have people come up to me frequently just average people. This is a unnamed Google developer and say things like RCS has so many operators. It's extremely confusing and then a couple weeks past and then the same
developer comes up to me and says I have this great idea for a new operator. Okay, that sounds good. So, is it the question then becomes, how do we get there? How can we help them create their own operator. First of all of this really need it. You saw their the decision tree that exists in RCS. Dev. I highly recommend going to that first to make sure that there isn't one of these many many operators out there that already exists that solves your problem for you. The second thing is I need to understand what approach is there are and which one did they should probably take for what
they're doing and then how they should test it. so there's another thing that this actually solves I really want to talk about it's it's kind of at the root of why I would like to talk about this topic and the other thing is that people will say things like this this app has so much Arts yes it's just like a wall of the d'urbervilles is very confusing or whatever they say something to that effect and what they're talking about is this I like to call the mega survivable so this was going scrolling it's like this huge chain of operator is an observable Creation in whatever and this isn't good
this is actually kind of bad well I think we all know that writing a giant mega function is bad and what you want to do with the giant mega function is you want to break it up into smaller functions and then compose them together where your giant mega function used to be right and so it becomes to your 1-year your function becomes more readable and two it becomes more testicle So you can do the same thing using custom operators. But first, I think it's important for people to understand. What is an operator. What's the anatomy of this thing?
Really? It's just a function that transforms in observable into a new observable operator that you use when you call it. It returns a function of this shape takes a source observable and returns a new type of observable. And in our observable pipe method, this is Justin pseudocode here. What what you're actually doing is you're just giving it a list of this this objects of the operator functions of this shape. So they're going from observable A to B B to C. And so on in the very last one that is returning the chain is the type that you get back from the the pipe method.
map operator for example your map out operator that use all the time would have a signature something like this it's a higher order function you give it some mapping function or what we call projection function and then it's going to return to you a function that takes an observable and returns an observable of a new type probably the type that the mapping function is mapping to so aren't you have a lot of operators made for you tons and tons as I keep saying so a really good choice to build your own custom operator is just to use the operators that you already have available
to you from RCS there really well tested their tested by the thousands of tests they test for weird edge cases and weird Corner cases they've got proper are handling that sort of thing so this is probably the best bet for most people a custom operator in this case might look something like this is the simplest possible and I can think of so, let's say we want an operator that just takes every nth value from an observable stream really what this is just a higher order function that's rapping the filter operator, right and inside the filter operator were saying for every index see if it's
divisible by n and if it is allowed to Value through And you would use it the same way you would use any other Arceus operator. This will just work some people don't realize this just works but it will and so now I've got this adorable of values between 0 and 9 same take every nth value and it's going to MIT 0 + 3 + 6 + 9 + 7 And the thing about it is this is what I would have in place if I didn't have that there and honestly between the two I think the custom operator is a service is always subjective but I do think the customer operators much more readable because the filter is going
to be there and you're going to have to now look at the filter and rock like what is this filter really filtering aware the other one you can just read the name of it and provided that you gave it a good name. You understand what it's going to do not to mention. Now, you can test it separately and to test it you can do something as simple as this you can just provide a mock observable which could be an observable of that synchronously gives you some values in a certain expectations on it or you could even use a subject so you can kind of see what it does overtime but still in a
synchronous way. Technically, it's a sequence. You signed it up and later on you called, but whatever. I'm not going to split hairs on that. But let's take a look at it more common problem not all of us are trying to get every nth value. Right? I think this is more common in in an angular app. You have situations where you're saying. Hey, I've got some subjective button clicks and I'm switch mapping that into I get and then I've got some are handling inside of there. And then you have a component that's got this on there, you know, five six times, right if this is a very common thing or
maybe even have a service that's doing this. So we can extract something like say that are handling right off. This is probably something that gets reused throughout your app. You got the same sort of pattern over and over again. I was very simple in this is no log the air and then returned empty saying I live handle this is fine and we can extract that out with a higher order function. I'm going to call log error and literally I just wrapped exactly the same thing that I was calling before and now we can take this and making it is in automatically in my opinion. This is much more
readable, especially if it's sitting next to dozens of other lines of code that we're doing the same thing before now you have it. It's all centralized. Right and you're you're sharing this this Central functionality. So if you change it in one spot it it changes for everybody. But we can do more here and you can divide this up. Anyway you want these are Primitives. This is just my dealer's Choice cuz I'm the one on stage, but I could take the switch map out. If I so chose and say it will what if I just made a custom operator that said I'm in a provided they should be client
and I'm going to say it's going to get the latest value from this year while using this agency client and it's got the the are logging wrapped into it. And now we're down to this. So now we have something I think it's even more readable where we can say. Look every time you click the load data button. You're going to go get a value from this this this endpoint right? I'm going to make an issue if you request. So we can take that even further. If we wanted and now what we have is effectively an operator, that's that's kind of a service for you and you can test this in isolation
by passing it amok httpclient and passing it and applying it on a mock observable and seeing what you get out of it without having to use too much in the way of messing with dependency injection or anything else, but more importantly your code now becomes. Hey every time I click load data. I want to go get GitHub users. Now in this case, I'm not really using the value that comes from those clicks because it's not something that I actually need to go load the data, but if I was using say I had a stream of user ID and I wanted to go load GitHub users there. It's important to
make sure that the name of my operator reflects the fact of like how it's going to use that that died you coming in. So that's again Personal Taste, but I recommend making sure that you add something into the name of your operator to show that So what this is all using operators that already exists in arcgis what happens if you if you can't use existing operators to do what you want to do. I don't know what you're doing there so many but it's it's plausible that you run into some some case where you need your own custom operator. You can build an operator from scratch. This is a recipe for
apple pie if someone wants to make that please invite me over. So if I was going to build an operator from scratch the one I go to for as good as the easiest one for people to kind of comprehend is Matt. So if I was going to build my own custom map operator, it would be a function that look like if it's a function that takes a function. That's that that function it takes is your projection function that you're going to use to change the value and it has to return this operator function. So function that takes a source of gerbil and return returns a new observable of a different type two type
being what were transforming to And inside of that initialization function we passed through our observable Constructor. Where do we need to subscribe to the source? So this is this is kind of the essence of operators. What they do is they they return a new observable that when you subscribe to it subscribe to the source of survival. And what we subscribe with is in this case is going to be a new Observer and then observers main responsibility in the case of Mapp is going to take be taking the the values that are next in from the source. It's going to call the transformation function on that
value and then pass it along to the next subscriber down the chain cycling next with the results. The other thing that's important as we need to make sure we're following through our air and completion channels. Otherwise heirs will not be handled handled properly after your custom operator. 2B sorts of operators what I just showed here. Are there they're all you really need most of the time honestly, this is this is just fine. They are wet date. There are a few gachas. I would wager to say the majority of people in here don't have to worry about this guy. She's but I'm still going to go
over. the couple of minor issues that exist with this map of limitation that I just showed you are one what happens if a user in this is the most common problem that I see with people's custom operator that are built from scratch what happens if a user provides a function that throw And the other one is it's not using Lyft and it's kind of a what like a lot of people understand lifting. That's a little less important, but I'll get to what lift is in a minute. Go take a closer. Look at what we did right here is the problem. So the problem is recalling a user-provided phone. So we have no
idea what this functions going to do. But if it's in your own app, you probably have some idea what it's going to do or if it's going to throw or not. So this might not be a big deal for just your app. But if you don't know who's calling this and what kind of function going to give you, we're not handling errors there. So what's going to happen if it throws is it just going to bomb nothing's going to tear down. This is not going to be great for sure you do. What you need to do is you need to wrap that user provided function in a try catch and there's a trick to it. You don't want to wrap
it's important to make sure that you're only try catching your user provided function. You take the air and send it down the subscriber are path or the result if there's no air and send it down the subscriber next. So what about Lyft this is a rarely understood thing as far as like why it exists what it does and what would its involvement is with operators lift is a method on observable and what it what it is is for one as a unified call site for all of our chances operators.
If you hooked into it somehow you could you could do some sort of analysis on like every single operator that is being used against observables. Nobody's exactly done that with any sort of debugging tool or anything like that. But theoretically it's there for the it's there for that. The other thing is it's responsible for creating all of the resulting observable instances. So one thing this gives us is subject when you when you use operators on subjects. They actually return subject. Typescript doesn't honor this because typescript doesn't have Tire kind of types and be thankful
for that cuz I think it would just confuse the hell out of all of us including me. You could tell you could operate on a subject in the casa to the subject and call next on it and it would work and the reason is lift 4 on is overridden on subjects to new up a subject and then do a cliff. It also helps Architects lyrics osrs. Yes, so it can identify. These are observable this adorable you're about to subscribe to is an observable that was created be an operator. The other ones the end of the operator chain. This other ones the source of the observable chain to architectural a for
the core team. It gives us some interesting guarantees as far as how we can architect architect. So this next section is really going to be kind of overkill for most people's needs. But I want people to understand it. And now it's my opportunity to tell people about it. This is what it looks like on observable it news up in the observable. It's set an operator property in a source property that are really kind of private apis to to RCS and then Returns the result. That's all it does. All right, but we have too many things called operator. You noticed I'm passing an operator
in here. That's actually an interview and interface I'm going to have for the sake of the stock. I'm going to refer to this as the operator applicator because it's more descriptive for what it actually does and the operator applicator is really just an interface looks like this. It had a commented on it that takes a subscriber which is our Downstream subscriber and the source observable that we're going to subscribe to and it also returned some teardown logic, which is generally a subscription could be a function also. And so if we are going to have a map operator like there are other map
operator that I just wrote use one of these operator applicator things. It would look like this. I'd have a commented on it and I'm going to subscribe to the source with the new Observer and still use my next function which is now part of this class, but you'll notice this part here should look very familiar because it's exactly what we did in Ardmore simplified operator. not really any different Another interesting thing about this class that had to call method on it as if you have a class to call method on it. You can also Define it as a function. It's just a
weird weird work of of this particular design. So that means you cannot pass the subscriber as the disagreements or it would be that this argument this contact contacts argument and you can also have the source of the first are going to come in. So that means that we can Define her math operator the same map of operator we had before this way where we say, we have the function map and its whole responsibility is to say I'm going to return the function to take the source observable calls lift on it with our map operator function and that's a higher order function that returns
map lifted, which is our operator applicator essential. That's doing what what the other one did inside. So this is how you would write a operator that uses Lyft and the benefits to this again or a little bit better architectural cooperation with with RCS and everything's kind of going to that sinful life and you would get custom pipes going through it because sometimes thing isn't as big of a deal in the pipe of the world that it was a big deal and we are doing. Chanting. Also, you want to make sure you have the air handling stuff. I admitted it
from the previous slide because it made it too small and hard to read my opinion, but you always always always make sure you're doing that. Butcher Taco the quick about testing your operators. Tessier operators you really have to identify the two different type of types of operators to test and its is this an operator just for my app or is this an operator for General consumption? If it's an operator just for your app, just test within reason these are unit test you're not doing this is not this is not a comprehensive test. It's really going to help your app is really just to make sure your
operator is doing what it's supposed to be doing you want to have integration tests for your application that cover most things including whatever the heck this operators doing. For operators that other RCS users are going to use you need to be exhaustive. You have to make sure you have to see what it's going to do with a synchronous observable what has to do with an asynchronous observable what happens if the observable completes right away what happens if you never get a value what happens if you retry the results what happens if you it's done with the hot observable or cold is herbal.
If you have an operator that has more than one observable involve like taking tell her something like that. Then this is going to you know X how many how many exponentially going to grow how many permutations of this you should test? This is what Marvel tests were designed for and I'm going to go over what a model test looks like here really quickly. Twitter to do models have to get to import the task scheduler marks. Yes / testing and then the next thing you need to do is make sure you knew up a new task scheduler before each test because each one has some underlying state that
a deal with and part of this as you have to pass through. This is certain equals function that you're responsible for creating. So if you're depending on what testing framework your urine, if you're in Jasmine, you would have a cert D people's and what it's going to do is it you're going to do a deep a quality comparison between the two guys that come in and what does gets used for it? All of our marbles get converted into trees of objects are arrays of objects that we end up doing deep deeper quality comparison comparison on to make sure that the test passes I need to write
the test. You would use this RX test. Run block inside of your test. And what we do in this is inside of the Run block reschedule a whole bunch of things to run in the test scheduler and at the end of The Run Block. It's going to flush the test scheduler. The task scheduler is this virtual queue of things that are supposed to happen overtime in it, and it puts things in the proper order. And it gives you some help her functions. Like this is a helper function that allows you to create a cold observable. So this is the most common thing that you would probably knew up inside of hear. What
this is is he passed to it? This kind of ASCII art diagram of what an adorable does each one of the dashes represents a single virtual millisecond or a single virtual unit of time and each one of the characters represents the emission of a value from the observable. It will default to be just a single strand of Single Character string of whatever you had their soul in this case a you can pass custom. Used to it though as a minute and then finally in this particular when we have this this pipe and pipe signifies the emission of complete so it's like you're you're adorable call complete
and said I'm done if I don't have a pipe here and just a dash that just means that goes never going to give you any more values for that never completes. And then for our what we're going to start against a result, we can just use marbles. It's it doesn't necessarily have to be in observable. So we just use marbles. You notice how these spaces at the front there the spaces don't count as anything and that it's like that so you can line things up to make sure that you've got all of your marbles kind of lined up vertically so you now you've got kind of a visual chart of what's
happening over time. So with me the values are going to go one to one at the same time a map to be synchronously. So it's going to happen in the same frame and then we test we use expected durable and we passed our observable to it having been modified by owner operator. So we're using map and route map all the values to be and then we expected to be the result of me just passing a result string in the to be block. That's really it for for mobile testing for this. If you want to use an error a pound sign represents an errand, so instead of a pipe you do a pound sign and that's how your test
what happens if there's an air. I'm more interesting marble test might be something like delay here. So delay is more interesting because now I'm using this time helper to show like, okay. Well, here's five virtual milliseconds or something like that. And we're going to kind of a just push things across as far as that that time is allowing us to I'm going over a little bit. So I'm in a hurry. Here I got take until this is just an example of having to observe was composed together. We see our Notifier notifies exit a certain time. And the expectation is that our result will complete at
that moment. And then finally, if you want to pass different values to this, you can just have a dictionary and look up that says a is going to be 250 is going to be forty thousand or whatever in here and you can you can use that in those two to be blocked and in your cold declaration up there and that way you can use other guys in these guys could be anything they could be other observables if you wanted as well. So when you should use marble tests, you should you do when your testing operators you should use it when you're testing event coordination maybe in your components or things that
have timing related function out of their kind of hard to get to reason about her test likes a server. That's really slow sometimes but not other times a few people in my workshop. We did that. But important takeaways from this this talk. I want people to Remember The Operators are just functions. You can create them literally to solve your problems to to make your code more readable. You just need to unit test in a little bit. Recommendations for from me are to try to use existing operators when you can they're tested very thoroughly. They handle user functions properly.
It's your really that's really your best bet. You don't need to try to you know, Piper Optimizer code by creating all your own operators and most likely anyways operator for others to use test them extremely thoroughly try to think of every Edge case you can and you have to make sure that you're handling errors and user-provided functions of the number. One thing. I see people trip on use marble tests in those cases. It's a great way to test your operator's but do not overuse Marvel. If you do not need to test everything in your application with marble test. That's nuts. I've seen people
Buy this talk
Access to all the recordings of the event
Buy this video
With ConferenceCast.tv, you get access to our library of the world's best conference talks.