Functional Programming with Angular

Angular.js has paid my bills for about 4 years now. It’s a behemoth framework commonly adopted by larger organizations wishing to get into the rich web client space. The larger organizations come from “tried and true” Java or C# frameworks like Spring, Struts, Dropwizard, MVC.net, and others I’m sure I’ve missed. If so, a lot of the trimmings that Angular has are very comfortable to you. There’s a routing mechanism, there’s an MVC-like structure, templates, and you have means of making things “private” in this icky JavaScript that looks so goofy and unsafe.

It’s no secret I’ve been happily learning, applying, and shouting out about functional programming. Prior blog posts, a functional programming podcast that I appear in, and lots of things that show up in my Twitter feed all point to this.

Angular and Functional programming

How do Angular and functional programming relate? Well, not directly. If anything there’s an inverse relationship. Angular wants you to mutate a bunch of things, whereas functional programming excels at working with immutable data. Angular has big OO-like constructs, whereas functional programming steps away from all of core things about OO (encapsulation, polymorphism, and inheritance). I have a personal project written in Angular and at time of writing I’m still writing Angular code for my day job. A lot of us can’t choose what we will be using for 8 hours a day. In the case of my personal project, I’ve tried starting over, but there’s just too much I’d have to reimplement. I didn’t get far with the reimplementation before my motivation died out. Momentum is an important consideration with any body of software.

Checklist to get functional

As I’ve been working with Angular more and more, I find myself using its features less and less. Things like factory and service are virtually unheard of. For my personal project I’m becoming more and more tempted to use something like angular-react-directive to push my template work to. There’s a few things that had to come together to do that.

Get on a package manager

I really like Webpack. Like any tool in the JS ecosystem, it takes some time to get setup the way you want it, but it’s very worth it. Browserify seems like it might work too, but I feel like Webpack configs tend to be very light, and more importantly standalone. This will make it easy to implement step 2 below.

Export all the things

Use the import and export syntax to provide and consume simple functions. If you actually need an Angular service such as $http, have your function take it. If it gets onerous, you can always partially apply it out. When you do this, your unit tests no longer need to worry about the order of $provide and inject. The ritual required to put things together before you can even run your test is small to non-existent. The standard ritual to Angular’s unit tests can become so large that I see teams start heavily applying DRY to the tests. Now the tests are small applications of their own that probably also need tests.

Demolish privacy

Javascript doesn’t directly have a private you can use to prevent outsiders from touching stuff you own. Instead you can do some scope trickery that essentially pulls off the same thing. You’ll find this code is the hardest to test, and I’m guessing also the code that breaks the most. Be aggressive about pulling out bits of that into standalone functions that don’t need a this and don’t mutate anything. Then it becomes easy to convince your team that making it public is harmless. If it really needs to mutate something, make the function immutable and then in the place it was used, perform the mutation. Getting perfect immutable code in even staunch functional languages is impossible, but you can quarantine the areas needing mutation.

Pass more parameters

The lengths OO goes to in order to prevent passing parameters around is extraordinary. Partial application handles this plenty well. Favor passing in more parameters, and keep the “config” parameters towards the front, with the “data” parameters towards the back. This makes partial application easier. Be aggressive about passing functions into the function as a means of “specializing” your functions. Think about map: It takes a list and a function that transforms each element in the list. map doesn’t know anything about your application. The function you pass map can be more intimate, however. This is how map can be so useful yet so generic. Remember that “abstract” and “generic” being dirty words is a product of traditional OO beliefs. If you find that you have code that operates on things that differ by name alone, you are doing duplication and you are looking at a great candidate for a generic plus specialized functions to add.

Pass functions, not objects

Sure, you can pass along $http to your restful calls and go from there, but why not pass a function instead such as $http.get, or simply use $http in its function form? Now your rest code becomes vastly more generic. You take a function that maybe takes some config but always returns a promise. Can you mock that? Can you give it something that didn’t originate from $http? Sure, at some point you probably want to pull in $http, but you can push that off for as long as is reasonable. Your business logic needn’t even know it uses Angular, and you’re not wrapping a bunch of stuff to accomplish this. When you pass functions, you specialize the function. In the functional world we call these Higher Order Functions, which are basically functions that take functions. Normally abstraction is a dirty word in the OO community. Consider that virtually anything that’s an abstraction in OO is really just wrapping. True abstraction is stripping away all of the things that are unimportant to your code such that the core concerns are laid bare. In the case of map, it has no idea what your lists are for, what elements are inside of them, and how those elements work. It just knows how to flow over the list and produce a copy. The morphism provided (a function that goes from T to U) has the true knowledge (hence providing the specialization). All map knows is the function must go from T to U. Is map a dirty abstraction? I think not. The fact that map does so little is what makes it so generically applicable to many situations.

Mutate state at the last possible second

$scope needs to be written to in order for meaningful things to happen in Angular. If you can wait until the last possible moment to mutate $scope, then that means everything prior to that mutation is functional - potentially pure (per the functional definition). A warning though: Be careful about binding function calls to the scope directly via the template system. Angular does a === on the results of a function binding, and it will continue running the $digest cycle until the data from the bindings stops changing. Immutable transformations (map=/=filter are easy examples) often emit a new object or list on each call. Angular will see your immutable structure is referentially different from your last immutable structure and mark it as changed even if it’s the same data. You’ll see that the $digest cycle has ended with about 10 runs and an error saying it’s given up. To avoid that, I find keeping these assignments in the controller is a very safe thing to do.

Added benefit: Unit testing

You’ll find that taking these steps will make unit testing in Angular vastly easier. You don’t have to worry about structuring your tests such that $provide is always used before inject, because you don’t need to pull in these Angular objects to perform a test. Just pass in functions that perform an outcome. Sure, you can use your favorite mocking library, but the amount of ritual involved in a mock takes on its most slim form. If Javascript unit testing is still fuzzy for you, check out my post on [BROKEN LINK: 2017-05-17-unit-testing].

Conclusion

Angular does a bit to discourage functional programming, but you can side step most of the hard stuff. Once you’re doing most of your work in functional land, you can step up your unit testing and enjoy some solid benefits that help you reason about your code.