Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> My personal issue with rx.js is that BehaviourSubject is leaky

RxJS best practice is that BehaviorSubject should be used infrequently to never, mostly as "internal plumbing" to things like building your own mini-Operators, and that they should never escape an API boundary. If you need to pass a BehaviorSubject to a consumer you always .asObservable() it and the consumer must treat it like a regular Observable (your API boundary is always Observable<T>, never Subject<T>). (BehaviorSubjects are an imperative "back door" that breaks building things the reactive way.) That's one of my biggest personal problems with the Angular core libraries is how many BehaviorSubjects leak out everywhere in that API design. EventEmitter is a big giant BehaviorSubject. The Routing APIs leak BehaviorSubjects. Angular's "Reactive" Forms leak imperative BehaviorSubjects all over the place. The bad use of BehaviorSubjects by the core libraries leaks to the rest of the Anuglar ecosystem and there are so many "Angular best practices" that are "RxJS worst practices" purely from these early API design decisions.

> We found that rx.js is bad for coordination.

RxJS is great at "coordination". It requires a different mindset. Angular is awful at helping you get into that mindset. Angular's HttpClient is especially "bad, heavy promises masquerading as Observables" which makes it hard to think of queries/API fetches as events that can return refreshed data over time (streams of fetch results), more similar to those user interactions you saw good results from. There's a lot of useful coordination operators in RxJS beyond `switchMap()` like `mergeAll()` and `concatAll()`. If you think of a stream of request events flowing into a stream of response events flowing into a state machine (possibly with a very simple, similar `scan` to your user interaction model to reduce your state over time, a little like the "redux" pattern [1]), RxJS can be brilliant for "coordination" as data arrives.

(Angular kind of sets it up to fail. With how HttpClient works. With how Async Pipe works and isn't the default.)

> or you put some `tap` or `subscribe` with `takeUntil`

This is also where Angular "best practices" and I diverge, and I think also stems from RxJS "worst practices". RxJS best practice is also to use `tap` as infrequently as possible. It's a worst chance escape hatch at best. RxJS best practice is also the `subscribe` as "late" as possible and also as infrequently as possible and that you never have a `subscribe` without an `unsubscribe` to clean up resources, including memory. (Which can be very important if you are trying to do everything the reactive way. You can move all your setup into Observables, including the setup and teardown of vanilla JS components.) The overuse of `subscribe` in Angular components seems one of the biggest obvious reasons why so much of the usage of Observables in the Angular ecosystem looks like bloated Promises. (Which is directly a bad example set by the core library's own HttpClient.) (I also think some of Angular "best practices" uses of `takeUntil` aren't great either. I was taught that Observable completion should "mean something" as it's a key event in the stream, and shutting down Observables early mean you miss later events.)

If Angular's template language took Observables directly, without needing an "async pipe", most of those manual subscribes would just vanish in an instant. Most of the needs for `ngOnInit` and `ngOnDestroy` "lifecycle events" disappear. (As Observables have lifecycle events already in subscribe/unsubscribe setup/teardown.) Angular could have not needed Zone.js at all nor its complex "Change Detector" apparatus. Angular could have done smart things in coordinating Observable observations by templates. (A lot of what React's last several major releases have been about in building its Concurrency, Suspense, and related systems out have been about among other things throttling "non-important" DOM updates together to things like requestAnimationFrame and doing very complicated work under the hood to set all that up from VDOM changes. In an Observable world you can pipe a `mergeAll()` through `debounce(0, requestAnimationFrameScheduler)` and get things like that "really easy".)

I wound up trying to encode all of my personal best practices for writing powerful, reactive components in Angular into an opinionated reusable "component framework": https://www.npmjs.com/package/angular-pharkas

I haven't yet found a good way to encode my mindset to "reactive service classes" in Angular, in part because "it feels obvious" to me and isn't really a pattern so much as a mindset, which I know it is exactly not obvious or lots of people would be doing it and Angular's ecosystem would be less full of bad examples. Probably a key place to start based on the above conversation is that I almost always wrap any calls to Angular's HttpClient in a "forever Observable". Whatever the input is to call that HTTP API, whether it is a refresh signal or some sort of other input event (Observable) hide the `switchMap()` in an Observable of results over time. Most "dependent" data streams are coordinated sometimes as simply as a `combineLatest()` and others are `scan()` reductions (even full "state machines" in some cases of those reducers). Everything is returned as Observable<T> and never any Subject<T>. Everything flows into the Components and `subscribes` are as late as possible (and these days hidden entirely away in "Pharkas" binds). Learn when to `share()` observables (or more often `shareReplay(x)`) and reuse existing Observables rather than build new ones.

I don't know how much that helps. I've been able to wring a lot of good reactive programming out of a massive Angular frontend, but I've been fighting Angular itself (and the terrible debugging/performance experience of the gross, unnecessary Zone.js), and the greater ecosystem of "Angular best practices" the whole way. Every Junior Developer with Angular experience that looks at the code for the first time generally thinks it is readable but that "it looks nothing like Angular I am used to". It's definitely not "Angular best practices" as people are learning them today.

Also, a useful related tip for removing most uses of `tap`: install `rxjs-spy`. It's great. It provides a very simple `tag('some-observable-name`)` operator that is a no-op in Production builds and in Development builds gives you a `window.spy` toolkit that lets you log events from tagged Observables (or a regex matching tagged Observable names) or even set debugger breakpoints at tags.

[1] Though I tend towards "lots of little observable streams" over combined ball of single state refiltered back into little observables like in the "proper" "redux" pattern or how tools like NgRx try to implement that in the Angular world.



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: