Share some Observable love (how share() works in Observable + angular2)

Summary

I’m still relatively new to RxJs (5). They have some good documentation (more in progress) but a lot of it is just experience and understanding the mental model. This blog is about the .share() operator and how it impacts angular2 especially regarding the async pipe. The share() operator allows multiple streams to be watched/subscribed but share the same underlying stream so that you only have 1 execution stack (think http calls over the wire where you may have many consumers but only want a single call to be made for the many consumers).

Marbles

First, for common RxJS/stream visuals .. check out RxMarbles. It’s a REALLY good way of visualizing stream operations (which is how I tend to think of Observables … streams to me seems a more natural word for me). That said … no marbles for the share operator….

ASCII Art (lo-tech marbles)

Since there’s no fancy RxMarbles, I’ll resort to lo-tech ASCII diagrams for description here (also for a great intro to streams check out this gist). Out of the box, each subscriber will get its own reference when subscribing to a stream

(stream that has 1, 2, 5 values streaming down)

Subscriber 1:
-----1-----2-----5-----|->

Subscriber 2:
-----1-----2-----5-----|->

The above is a basic example where there is 1 stream and 2 subscribers. Each subscriber will get a copy of the stream and process independently. That allows each to act independently and unaware that anyone else is doing anything, including any ‘side effects’ on the stream like map processing or other operations.

While this is great for keeping consumers consistent and independent, it’s pretty terrible for performance if you are doing things in the stream processing that involve network, heavy latency, or performance intensive computations. This is where .share() comes in. If we take the above diagram and apply .share() to it, it becomes

(stream that has 1, 2, 5 values streaming down)

                         Subscriber 1:
                     /-------|->
                    /
-----1-----2-----5-|
                    \
                     \-------|->
                         Subscriber 2:

If instead of numbers being sent on the stream we think of each number as an operation on the stream to OBTAIN the numbers (such as a map, perhaps deriving some deep mathematical computation like prime number generation) it starts to make more sense in understanding why we want to use share().

Example/Plunker

I have an angular2 plunker that illustrates things. In the plunker I am making an http call with some latency (anywhere from 1-2seconds). I have 2 calls in my angular2 service

    /**
     * Function will return an observable with the data requested. This can be shared across
     * subscribers and will not cause extra http traffic.
     */
    getDataShared(postNum: number): Observable {
        let calls = 0;
 
        return this.http
                   .get('http://jsonplaceholder.typicode.com/posts/' + postNum)
                   .do(
                     () => {
                       console.log("side effect on shared");
                     }
                   )
                   .map(
                      (res) => {
                        let json = res.json();
                        calls++;
                        json.networkCalls = calls;
                        console.log(JSON.stringify(json));
                        return json;
                      })
                   .share();
 
    }
    /**
     * Function will return an observable with the data requested.
     * There is no share operator used here so each subscriber will result in the entire stream firing.
     */
    getDataNotShared(postNum: number): Observable {
        let calls = 0;
 
        return this.http
                   .get('http://jsonplaceholder.typicode.com/posts/' + postNum)
                   .do(
                     () => {
                       console.log("side effect on not shared");
                     }
                   )
                   .map(
                      (res) => {
                        let json = res.json();
                        calls++;
                        json.networkCalls = calls;
                        console.log(JSON.stringify(json));
                        return json;
                      });
 
    }

I also have bindings via the async pipe in angular2

 <table class="table">
   <tr><td>UserID:</td><td>{{(dataServiceObservableNotShared | async)?.userId}}</td></tr>
   <tr><td>ID:</td><td>{{(dataServiceObservableNotShared | async)?.id}}</td></tr>
   <tr><td>Title:</td><td>{{(dataServiceObservableNotShared | async)?.title}}</td></tr>
   <tr><td>Body:</td><td>{{(dataServiceObservableNotShared | async)?.body}}</td></tr>
 </table>
 <table class="table">
   <tr><td>UserID:</td><td>{{(dataServiceObservableShared | async)?.userId}}</td></tr>
   <tr><td>ID:</td><td>{{(dataServiceObservableShared | async)?.id}}</td></tr>
   <tr><td>Title:</td><td>{{(dataServiceObservableShared | async)?.title}}</td></tr>
   <tr><td>Body:</td><td>{{(dataServiceObservableShared | async)?.body}}</td></tr>
 </table>

If you run this example and look at the browser console what you’ll see is that for the NON shared stream the stream reaches all the way back to the originator of the data, the http call, for EACH subscriber (the subscriber is the binding in angular2 via the async pipe).

side effect on not shared
{"userId":1,"id":3,"title":"ea molestias quasi exercitationem repellat qui ipsa sit aut","body":"et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut","networkCalls":1}
side effect on not shared
{"userId":1,"id":3,"title":"ea molestias quasi exercitationem repellat qui ipsa sit aut","body":"et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut","networkCalls":2}
side effect on not shared
{"userId":1,"id":3,"title":"ea molestias quasi exercitationem repellat qui ipsa sit aut","body":"et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut","networkCalls":3}
side effect on not shared
{"userId":1,"id":3,"title":"ea molestias quasi exercitationem repellat qui ipsa sit aut","body":"et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut","networkCalls":4}

You can also see that the ‘side effect’ processing is called 4 unique times. While this may be great in some cases, it is clearly NOT good when talking over high latency operations, such as http, or with code that is CPU intensive. What we’d prefer to do is share the stream across all subscribers and re-use the upstream processing – only consume the end result. This is what the .share() operator does.

side effect on shared
{"userId":1,"id":3,"title":"ea molestias quasi exercitationem repellat qui ipsa sit aut","body":"et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut","networkCalls":1}

You can also notice the very real side-effect of the 2 different stream processing types by looking at the latency generated when binding the UI. Since there are 4 different http calls being made in the non-shared example and only a single call in the shared example the UI itself demonstrates how dramatic the binding time is. Check out the below animation

share operator on stream processing

You can see I put the post number ‘3’ into the non-shared stream and each area of the UI where I have an async pipe doing the binding is binding individually and slowly. This is because there is a network call taking place for EACH async pipe. Whereas the shared stream makes 1 call and the ENTIRE UI binds in 1 operation afterwards. You can also see the console logs indicate that my ‘side effect’ is taking place multiple times for the non-shared stream whereas for the shared stream you notice 1 ‘side-effect’ taking place.

Conclusion

The share() operator isn’t applicable in all cases, but for angular2 UI binding when utilizing the async pipe I believe it’ll be used on MOST cases where a single underlying stream is used. ESPECIALLY for angular services that make http calls.

I hope this helped you understand the share() operator in RxJS and also how it impacts performance for any consumer especially angular2 and the async pipe.

Thanks for reading!

Ubuntu 16.04 Release + Dell XPS 13 9350

UPDATE 5/11/2016 – The flicker is apparently Chrome only and has a report to Google. If you use firefox you won’t see this screen flickering. You can also run Chrome via “google-chrome –disable-gpu-driver-bug-workarounds –enable-native-gpu-memory-buffers” and the flickering will stop. You can also edit the desktop file which will allow you to launch the app as you would normally through the launchers – it lives at “/usr/share/applications/google-chrome.desktop”.


I’ve blogged before about my experiences with the new skylake XPS 13. I’ve been VERY happy with the laptop and wanted to give a status now that 16.04 is released.

Fn Key Behavior

All Fn keys work as expected. Notable (?) exceptions seem to be FFWD/RWND and the ‘search’ button (F9). The most important ones for me are display brightness and volume … those work perfectly as well as mute/keyboard brightness and wifi on/off. Interesting wifi on/off (which shares space with the PrtScr (print screen) key seems to favor printing the screen FIRST even though I have the Fn keys turned on. I’m actually not upset about this since I rarely need to disconnect wifi and would maybe use the PrtScr function now and again (normally I just use the Screenshot app so I don’t see myself doing either one honestly).

Suspend/Resume

Everything is working perfectly here. Plugged in or not it does what I have specified which is when the lid is closed it suspends.

Battery Life

I haven’t run any specific battery test with details, based on my experience this things is a champ. I get plenty of battery life on it no matter what I’m doing. On my System76 Galago UltraPro when I have it unplugged I can watch the percent indicator tick down on regular intervals. On this thing each tick is given up begrudgingly. I’d estimate easily 7hrs and maybe as high as 9-10. The ‘pro’ reviews peg the battery life in that range I’m definitely inclined to agree.

My usage is web browsing and programming with web editors/languages such as Visual Studio Code/Sublime/Angular2/gulp builds/etc. Not super battery hungry applications but the web includes youtube things now and again – which doesn’t seem to make much of a difference.

External Monitor

Saving the best for last… I’ve blogged before about the external monitor via USB-C. I bought a USB-C to Display Port adapter from amazon and when I last tried on a 16.04 Beta1 it didn’t work at all. This time as soon as I plugged it in it worked like a champ! Wanted to see how reliable it was so unplugged and plugged in … didn’t work. Again, didn’t work. Unplugged the power from the laptop and tried again .. WORKED!

So summary here is that it seems to give you an external connection just fine ONCE per power setting. 🙂 This is a completely weird issue … but if plug in your USBC plug and it didn’t work, unplug the laptop (or plug it in depending) and then try again and it should work. This makes me hopeful that it will be resolved in future updates but at least for now there’s a workaround – as goofy as it is.

Monitor ‘Flicker’

It was noted in a previous blog by Luis:

NOTE 4/7/2016: Read the comments for good feedback from others trying. Luis noted: “..before attempting to install one needs to boot and in the BIOS configure SATA-controller to AHCI (or Off).”. He also noted there’s a bug for the screen flicker issue.

The screen flicker issue still seems to be here occasionally. For now it doesn’t really bother me. It’s not constant and during the entirety of typing this blog in I haven’t seen any flicker – I even went to CNN and scrolled around and played a video and didn’t see it. I think it tends to happen when browsing the web and there’s lots of ads on a page or video going on or when scrolling quickly through the browser. I can’t really establish a pattern because it doesn’t happen frequently enough – just putting it in here so that it’s a known issue albeit an infrequent one.

Summary

It’s a shame the laptop isn’t 100% given the external monitor issues but I’m still absolutely thrilled with it. If I’m not sitting at my desk using my System76 Galago UltraPro, I’m using this thing because I don’t have to sweat battery life and the form factor and performance are killer. The trackpad works perfectly (remember to install libinput!), keyboard is great to type on … it’s a dream to have honestly.

Enjoy!