molily Navigation

Things I’ve learned: Angular, TypeScript and RxJS

Exploring programming paradigms in different JavaScript stacks

In this post I’d like to share my experience with web development technologies I’ve encountered recently. I won’t give you a thorough introduction to these technologies, only describe my personal viewpoint.

Back story: Backbone, Chaplin, React

I’m developing websites since the late 1990s, and today I’m a mostly writing front-end code with a focus on JavaScript. I’ve never actively chosen to become a “JavaScript expert”. Ten years ago I was a regular in the German Selfhtml Forum. When JavaScript gained traction in the wake of “Ajax”, more JavaScript questions were posted and I tried to answer them. By looking into other people’s problems, I’ve learned the depths and shallows of JavaScript.

The company I work for, 9elements, used JavaScript quite early for animation, visualization and rich interactions. In 2012 we released Chaplin, a Backbone-based architecture for JavaScript single-page applications. Later we discovered the power of React and Redux, and they are still the tools of our choice. In a recent smaller project, we substituted React with Preact, and we were happy about that choice.

The React and Redux ecosystem has many good practices built-in that come in handy when developing larger web apps. For those who’ve used plain JavaScript, jQuery or Backbone/Chaplin, the simple and clear component model of React is an epiphany.

Giant leap: Redux

Recently I was wondering which web development technology improved the way I work most in the last years. There are certainly a few leaps, like Redux, but I think what made web dev easier is the sum of different small tools. Libraries like React and Angular, compile-to-JavaScript languages like Babel and TypeScript, build tools like Gulp, Browserify, Webpack, linters like eslint, testing tools like Karma etc. Today it’s much easier to produce good code.

Most people would probably say React had a bigger impact on their work than Redux. For me it’s the opposite; Redux was the eye opener. I’ve always struggled with application-wide state management. Chaplin and Marionette made it easier to keep state in central models, but every non-trivial app ended up as a hairball of internal dependencies and confusing model-view bindings.

Redux and the underlying Flux architecture feature ideas of functional and reactive programming and reintroduced these paradigms into web front-end coding. Redux allowed to talk about application state in formal terms again. When something goes wrong in your app, it’s easy to find the cause. Moreover, the parts of the architecture are clearly separated and relatively easy to test in isolation.

Side effects in Redux: Thunks and Sagas

Still, in large React projects I struggled with writing adequate unit tests for all parts. In my opinion, Redux has a blank space when it comes to side effects. A side effect is for example when your app loads data via HTTP or saves data locally. That is, when it actually gets useful. The easiest solution, redux-thunk, is cumbersome to test. And it opens Hellmouth right in the middle of Sunnydale High. More and more logic creeps into action creators while the simple idea of creating actions – const createFoo = (value) => ({ type: FOO, payload: value }); – gets lost.

There are ways to solve the side effects conundrum in Redux, but each comes with a significant overhead. The most ambitious solution I have tried is Redux Saga. It is probably the most powerful and complex solution. But it comes at the cost of introducing a new programming paradigm: concurrency. A saga is a dormant background thread that wakes up briefly when you cast the right spell, then sleeps again while keeping its memory. Redux side effects are just a finger exercise for this powerful paradigm.

To make things more tricky, Redux Saga uses the clever hack of ECMAScript 6 generator functions to create pauseable, asynchronous functions that are able to return multiple values. These values are merely descriptions of what should be done (e.g. “call function fetchUser with 1”, without calling the function). That makes them easy to test. But a clever hack using brand-new language features is not something you’d want your team to work with.

Another well-known drawback of Redux is that you need a lot of boilerplate code if you have multiple similar records with CRUD actions that are transmitted over a RESTful HTTP API. We used helper functions to create reducers, action creators and the corresponding tests. This felt like a step backwards from good old Backbone models that had these conventions baked-in. There are tools to implement model-based state management with Redux, but for me these approaches just don’t fit together.

Angular

This was just the prelude for the topic I’d like to write about. At 9elements, we do have standard tools and favorite development stacks. But we try to use the right tool for a job instead of just the tool we’re used to. In a recent client project, we have been using Angular 2+ and I had the pleasure to set up the architecture.

My impression was that the Angular and the React ecosystems are very close. The core concept are encapsulated UI components that have a template, scoped styles, well-defined dependencies, inputs/props and outputs/callbacks.

So I quickly felt at home when setting up an architecture with Angular. Over the years, we had to discover the best practices for React ourselves (e.g. pure components, Redux, reselect, Webpack, Babel, eslint, CSS Modules and CSS-in-JS, Jest, Enzyme, create-react-app etc.). It seems that Angular 2 together with angular-cli took the insights of Angular 1 and these common practices to put them in one framework and development environment. So I found Angular to be familiar right from the start and I was pleased that most things worked nicely.

Angular has its own quirks that make any app and any single component more complex than in React – at first sight. For example, dependency injection produces a lot of code that might scare off beginners. For me, the benefit was easy to comprehend, having tested React apps with home-grown dependency injection. In a typical Angular app, there is one obvious way how to test a component, service or module. The spec boilerplate is even created by angular-cli. Having these conventions in place is a huge benefit. It makes it easy for a team to develop a good testing discipline.

Angular feels “enterprisey” with its several abstraction layers, meta-structures and boilerplate code. I will probably never grok all details of Angular. But all in all there were no big surprises for me. What I found challenging so far wasn’t Angular, but two adjacent technologies: TypeScript and RxJS.

TypeScript

TypeScript is a compile-to-JavaScript language that adds a static type system to JavaScript, by its nature dynamically typed. I’ve written statically-typed languages before, but TypeScript is a good mix of correctness, expressiveness and practicability. The Angular ecosystem provides type definitions that make it easy to add type annotations to common data structures and application parts.

TypeScript with strict null checks shows how sloppy a programmer you are. And it shows that common JavaScript API idioms – like methods overloaded with four different signatures – are hard to express with proper type definitions. This constraint is good since it forces you to use simpler object structures and function signatures.

Still I find it hard to express some structures in TypeScript. Using TypeScript is a constant investment, you need a certain discipline and will power. But the effort is worth the benefit.

Previously, I’ve used the JavaScript IDE WebStorm/RubyMine which offered statical analysis of the JavaScript source code. Navigating in the code base, refactoring and auto-completion was easier than with a plain text editor. With TypeScript and Visual Studio Code however, you gain all the productivity of well-known IDEs.

RxJS: Reactive Programming

It was a good decision by the Angular team to write Angular 2 in TypeScript, and to recommend TypeScript for every new Angular project. What I found even more radical was the decision to build Angular core parts on top of RxJS Observables. I’ve watched (functional) reactive programming grow from a distance, but I’ve never found a good entry.

The technologies I’ve mentioned here were quite approachable to me. React and Redux for example encourage some functional and reactive programming practices, but these are still quite basic and learnable. Similarly, TypeScript doesn’t offer anything fundamentally new that I haven’t seen in other languages before. RxJS though, and to a lesser extent Redux Sagas, open a window to a world of declarative programming that I haven’t visited before.

NgRx: Redux for Angular

So I’m struggling with expressing logic in RxJS that would be simple to implement imperatively. At the same time, I’m convinced that this way of programming has its clear benefits in some cases. For example, I’m using NgRx, a Redux implementation for Angular. NgRx is standing on the shoulders of giants and ships with a practical solution for the side effects riddle: NgRx effects.

Like “epics” in redux-observable, an NgRx effect maps an observable stream of Redux actions to a new stream of actions. Asynchronous side effects are possible by calling injected services. Most effects have this structure: “When action A occurs, make an API request with its payload, then dispatch an action B with the API response”. These effects are concise, easy to read and easy to test. Writing them efficiently requires a deep knowledge of RxJS though.

Reactive programming with RxJS feels natural with Angular and NgRx. At the same time it doesn’t integrate magically with Angular components and services, it’s still manual work.

For example, it’s easy to subscribe to an observable, but it comes with potential problems. Unsubscribing correctly isn’t trivial, and by the way, don’t unsubscribe quite so much. When using RxJS, you’re quickly lost in hot vs. cold observables as well as sharing and multicast. An observable can maintain subscriptions and act like a cache.

This reminds me of the event-driven architecture that Backbone, Marionette and Chaplin used for model-view binding and application-wide events. It was a step forward back then, but still required manual work and caution. With RxJS, you get even more power and complexity but expressed in a concise, somewhat cryptic declarative language that only a few people on earth speak fluently.

Conclusion

All programming concepts and libraries used for web interfaces have to deal with similar problems. As different as approaches like React and Angular, Redux Sagas and NgRx effects might be, they address the same problems using different programming paradigms. It’s great to learn new solutions and see conceptual similarities – both strengths and weaknesses. I’m glad to have the opportunity to work with these technologies.

Thanks to recent libraries and tools, client-side JavaScript has reached a level of maturity that allows us to write more robust web applications. The downside is that we’ve reached a complexity where it’s virtually impossible to understand the abstraction layers we’re using. Back in the days of jQuery and Backbone, it was possible to read the full source code and at least understand the overall structure. Today we reach for powerful programming paradigms that are not new but still unfamiliar to most web developers.