In an Angular project I’m currently working on I have a large service (let’s call it *Master* service) that is used in multiple controllers. It started out as a small service that was responsible for only a handful of things. But as the project went on I was adding more and more logic into it and one day I woke up to a service with over 600 lines of code and a bunch of not really related methods. Not good, especially because working with methods in this service was quickly becoming more and more difficult.

Deadlines were tight so spending a few hours on cleaning up the mess and updating references in all of the controllers wasn’t a good idea. What makes this idea even worse is that after the reference update I would need to retest everything to make sure it works. As the popular meme goes: ain’t nobody got time for that. But still, I needed to divide the code throughout multiple files so it would be easier to work with it.

After some thought the solution was pretty obvious. I decided to leave the *Master* service as a sort of an API that’s the access point to all the methods but to keep the actual logic in multiple lean services. While the whole idea isn’t anything new or revolutionary, it helped me a lot in the current project so I figured others might find it useful too.

Here’s how I approached that.

### Starting point – One Large Service

Let’s start with a Math service that has a few methods. Nothing complicated. That’s our *Master* service that we want to refactor. Please note that the Math service below is just a simple example to get the point across.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
(function(){ "use strict"; angular .module("app") .service("math", math); math.$inject = []; function math(){ this.cube = cube; this.isNegative = isNegative; this.isPositive = isPositive; this.isZero = isZero; this.pow = pow; this.square = square; function cube(num){ return pow(num, 3); } function isNegative(num){ return num < 0; } function isPositive(num){ return num > 0; } function isZero(num){ return num === 0; } function pow(base, exp){ if (exp === 1) return base; return base * pow(base, exp - 1); } function square(num){ return pow(num, 2); } } })(); |

Here are the unit tests (written in Mocha with Chai as the assertion library) for this service. Please note that I’m also using angular-mocks to instantiate the service in the unit test.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
describe('Math', function () { var math; beforeEach(module('app')); beforeEach(inject(function (_math_) { math = _math_; })); it('should square argument', function () { chai.expect(math.square(2)).to.be.equal(4); chai.expect(math.square(4)).to.be.equal(16); chai.expect(math.square(6)).to.be.equal(36); }); it("should cube argument", function(){ chai.expect(math.cube(2)).to.be.equal(8); chai.expect(math.cube(3)).to.be.equal(27); chai.expect(math.cube(5)).to.be.equal(125); }); it("should pow base to the exponent", function(){ chai.expect(math.pow(2, 3)).to.be.equal(8); chai.expect(math.pow(3, 4)).to.be.equal(81); chai.expect(math.pow(5, 5)).to.be.equal(3125); }); it("isZero should return bool value based on value given", function(){ chai.expect(math.isZero(0)).to.be.true; chai.expect(math.isZero(-5)).to.be.false; chai.expect(math.isZero(25)).to.be.false; }); it("isPositive should return bool value based on value given", function(){ chai.expect(math.isPositive(0)).to.be.false; chai.expect(math.isPositive(-5)).to.be.false; chai.expect(math.isPositive(25)).to.be.true; }); it("isNegative should return bool value based on value given", function(){ chai.expect(math.isNegative(0)).to.be.false; chai.expect(math.isNegative(-5)).to.be.true; chai.expect(math.isNegative(25)).to.be.false; }); }); |

That’s our starting point. The service does its job and unit tests pass successfully.

### Dividing logic into multiple lean services

Now’s the time for refactoring. We want the leave the Math service as the access point to our methods but keep the logic in multiple micro services. In the example above we can easily divide the methods into two groups: exponentiation-related (pow, square, cube) and sign-related (isPositive, isNegative, isZero). Let’s create a math.pow and math.sign services then.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
(function(){ "use strict"; angular .module("app") .service("math.pow", mathPow); mathPow.$inject = []; function mathPow(){ this.cube = cube; this.pow = pow; this.square = square; function cube(num){ return pow(num, 3); } function pow(base, exp){ if (exp === 1) return base; return base * pow(base, exp - 1); } function square(num){ return pow(num, 2); } } })(); |

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
(function(){ "use strict"; angular .module("app") .service("math.pow", mathPow); mathPow.$inject = []; function mathPow(){ this.cube = cube; this.pow = pow; this.square = square; function cube(num){ return pow(num, 3); } function pow(base, exp){ if (exp === 1) return base; return base * pow(base, exp - 1); } function square(num){ return pow(num, 2); } } })(); |

### Linking lean services as dependencies

Now the only thing left for us to do is to update the main math service. Here it is.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
(function(){ "use strict"; angular .module("app") .service("math", math); math.$inject = ["math.pow", "math.sign"]; function math(mathPow, mathSign){ this.cube = mathPow.cube; this.isNegative = mathSign.isNegative; this.isPositive = mathSign.isPositive; this.isZero = mathSign.isZero; this.pow = mathPow.pow; this.square = mathPow.square; } })(); |

That’s it. We divided the logic into two services so it’s easier to work with them. There’s no additional testing needed as the controllers and unit tests stay as they were.

Obviously, the given example is far from being compelling. But if you’re dealing with a much bigger piece of logic encapsulated in a service and working with it is getting more and more difficult, consider creating multiple micro services and reference them in your *Master* service. It takes no time and the benefits it gives are huge.

## Leave a Reply