Earlier this week, I realized that the EventEmitter class in Angular 2 Beta 6 was a sub-class of the RxJS Subject class; which, in turn, implements both the RxJS Observer and Observable interfaces. I was excited to learn about this; but, at the time, I didn't have a solid use-case for it. As I started to think about logger errors in an Angular 2 application, however, I thought it would be really cool if I could consume errors as a stream. That way, I could leverage all of the RxJS operators and do things like throttle the logging, check for uniqueness, and pipe errors into an HTTP request (which is also implemented as an RxJS observable sequence).
CAUTION: Unfortunately, the .post() request doesn't actually work on GitHub pages because it returns a "405 Method Not Allowed". The browser caches this 405 response and refuses to try again on subsequent errors.
This demo is really exciting for me because I got to experiment with three different aspects of Angular 2:
- Providing custom exception handling.
- Learning about RxJS streams.
- Learning more about making HTTP requests.
To override the core error handling in the application, we need to provide a custom implementation of the ExceptionHandler class. When we do this, our implementation will be picked up by the Angular 2 framework itself and used internally when wiring the entire application together. As such, any error that gets thrown in the Angular 2 application will be piped into our ExceptionHandler's .call() method.
The .call() method is the only instance method on the ExceptionHandler class and, is therefore, the only method that we need to implement. The core ExceptionHandler class does have one static method - .exceptionToString(); however, as I've argued previously, static methods are not part of the "class contract" in a dependency-injection framework. That said, we can most definitely leverage the existing .exceptionToString() static method within our ExceptionHandler implementation! I mean, Angular 2 already did a lot of work in that regard - no need to throw it all out.
As our .call() method is invoked by the application, we need to log the errors both to the browser console and to the server. This is where the concept of RxJS streams come into play. Internally, our ExceptionHandler implementation is going to hold an instance of the EventEmitter class which will act as our "error stream." As errors come into the .call() method, we are going to pipe them into the EventEmitter instance using the .next() method. Each error will then be passed down through the RxJS operator chain - getting mapped and transformed - until it is eventually posted to the server using the HTTP class.
We could have called the HTTP service directly from within our .call() method implementation. But, by piping errors through an intermediary observable stream, we really get to take advantage of the power of RxJS. In this case, we are using the .distinctUntilChanged() operator to only log unique sequences of errors. This way, if a user sits there triggering the same error over and over again (which, believe you me happens), we don't have to flood the error logs with so much noise - we log it once and move on.
With that said, let's take a look at the code. This demo has two links so that different errors can be triggered on demand. This way, we can see how arbitrary combinations of errors are handled by the ExceptionHandler implementation:
As you can see, in our .call() method, we're always logging the error to the console before we pass it on to the EventEmitter error stream. This way, it always gets logged locally but only gets logged to the server based on the RxJS operators. And, when we run this page and try to trigger a few errors, we get the following output:
As you can see, repetitive errors get logged to the browser console every time; but, only unique sequences of errors get logged to the server.
I could probably have put the console.log() call inside of a .do() operator within the RxJS chain. But to be honest, I am not yet sure where the line should be drawn - I'm not sure how much functionality I should try to shoehorn into a single RxJS stream. What I like about the current implementation is that the stream, itself, is focused entirely on HTTP logging. If I then mixed-in console logging as a concern, would that be to much? I don't know - still learning this stuff.
I still love the simplicity of Promises. But, the more I learn about RxJS streams, the more powerful they appear to be. I just hope that I don't accidentally take on the mentality of Maslow's Hammer, in which everything starts to look like a stream simply because I have the RxJS at my disposal.
Want to use code from this post? Check out the license.