Sunday, October 12, 2014

Let's Build an AngularJS App!

This weekend I had the pleasure of attending the Atlanta Code Camp. It was a fantastic venue with sold out attendance and a variety of speakers covering cutting edge topics. I always try to submit a variety of topics but due to popularity, AngularJS won again! I've watched many talks that break down various facets of Angular and even some live coding demonstrations, but realized there haven't been many that cover building an actual, functional app with unit tests.

I structured the talk so that I could easily go from start to finish in stages. In the PowerPoint deck you'll see I have actual git checkout commands that reference the various commits. This will enable you to roll forward to each step after you've cloned the repository. The result is a responsive health calculator app that scales well to a variety of page sizes, has built-in validations for inputs and provides you with basal metabolic rate (BMR), body mass index (BMI), and target heart rate zones (THR). The app was developed using a test-driven (TDD) approach with a total of 83 specifications. The GitHub repository has full source and instructions.

Here is the deck. I hope you enjoy!




Jeremy Likness

Saturday, October 4, 2014

AngularJS Tip: Using a Filter with ngClass

I was working on my presentation of building an AngularJS app for the upcoming Atlanta Code Camp and ran into an interesting scenario. The app tracks various variables and formulas and then displays them in a responsive fashion. I’ll post the deck and code after the camp. One formula is Body Mass Index, an indication of general weight and health. I use it as an example but it is not the most accurate and a lean, muscular person may register as overweight or obese on the scale (read my non-technical article 10 Fat Mistakes to learn what you should really focus on).

For the session I want to show a test-driven approach, so I start out by defining what I expect from the formula using Jasmine:

describe("Formula for BMI", function () {
 
    describe("Given a 5 ft 10 person who weighs 300 pounds", function () {
        it("should compute a BMI of 43", function () {
            var actual = formulaBmi({
                height: 70,
                weight: 300
            });
            expect(actual).toBeCloseTo(43);
        });
    });
    describe("Given a 5 ft 8 in person who weighs 120 pounds", function () {
        it("should compute a BMI of 18.2", function () {
            var actual = formulaBmi({
                height: 68,
                weight: 120
            });
            expect(actual).toBeCloseTo(18.2);
        });
    });
});

Next I define the formula and ensure the test passes (notice at this stage I haven’t even involved Angular yet):

function formulaBmi(profile) {
 
    // BMI = (weight in pound * 703) /
    // (height in inches) ^ 2
    var bmi = (profile.weight * 703) /
        (profile.height * profile.height);
    // round it
    return Math.round(bmi * 10.0) / 10.0;
}

So that:

  • Formula for BMI
    • Given a 5 ft 10 person who weighs 300 pounds … should compute a BMI of 43
    • Given a 5 ft 8 in person who weighs 120 pounds … should compute a BMI of 18.2

Then I set the expectation it will register with Angular as a service:

describe("BMI Formula service", function () {
    var formulaBmiSvc;
    beforeEach(function () {
        module('healthApp');
    });
    beforeEach(inject(function (formulaBmiService) {
        formulaBmiSvc = formulaBmiService;
    }));
    it("should be defined", function () {
        expect(formulaBmiSvc).not.toBeNull();
    });
    it("should be a function", function () {
        var fnPrototype = {},
            isFn = formulaBmiSvc &&
            fnPrototype.toString.call(formulaBmiSvc)
            === '[object Function]';
        expect(isFn).toBe(true);
    })
});

… and I register the function with Angular to make the test pass:

(function (app) {
 
    app.factory('formulaBmiService', function () {
        return formulaBmi;
    });
})(angular.module('healthApp'));

So that:

  • BMI Formula Service
    • should be defined
    • should be a function

To show the index I want to translate it to the scale it provides. Anything under than 18.5 is considered underweight, while anything over 25 is overweight and over 30 is considered obese.

Now I can write the tests for a filter that satisfies these conditions:

describe("BMI filter", function () {
    var bmiFilter;
    beforeEach(function () {
        module('healthApp');
    });
    beforeEach(inject(function ($filter) {
        bmiFilter = $filter('bmi');
    }));
    it("should be defined", function () {
        expect(bmiFilter).not.toBeNull();
    });
    describe("Given BMI is less than 18.5", function () {
        it("should return Underweight", function () {
            var actual;
            actual = bmiFilter(18.4);
            expect(actual).toBe('Underweight');
        });
    });
    describe("Given BMI is greater than or equal to 18.5 and less than 25", function () {
        it("should return Normal", function () {
            var actual;
            actual = bmiFilter(18.6);
            expect(actual).toBe('Normal');
        });
    });
    describe("Given BMI is greater than or equal to 25 and less than 30", function () {
        it("should return Overweight", function () {
            var actual;
            actual = bmiFilter(26);
            expect(actual).toBe('Overweight');
        });
    });
    describe("Given BMI is greater than or equal to 30", function () {
        it("should return Obese", function () {
            var actual;
            actual = bmiFilter(31);
            expect(actual).toBe('Obese');
        });
    });
});

… and the filter itself:

(function (app) {
 
    app.filter('bmi', function () {
        return function (input) {
            var value = Number(input);
            if (value >= 30.0) {
                return 'Obese';
            }
            if (value >= 25.0) {
                return 'Overweight';
            }
           if (value < 18.5) {
                return 'Underweight';
            }
            return 'Normal';
        };
    });
})(angular.module('healthApp'));

So that:

  • BMI filter
    • should be defined
    • Given BMI is less than 18.5 … should return Underweight
    • Given BMI is greater than or equal to 18.5 and less than 25 … should return Normal
    • Given BMI is greater than or equal to 25 and less than 30 … should return Overweight
    • Given BMI is greater than or equal to 30 … should return Obese

Now that I have the filter, I can use it to show the text for the current BMI:

<h1>BMI:</h1>
<
h2>{{ctrl.bmiValue}}</h2>
<
b>{{ctrl.bmiValue | bmi}}</b>

This will show the title, the actual numeric BMI value and the designation of Underweight, Normal, Overweight, or Obese.

The problem I have is that I want to apply a class and show it in light red for the underweight and overweight conditions, and dark red for the obese condition. I could use ng-class and simply redefine the ranges like this:

<div ng-class="{ obese: ctrl.bmiValue >= 30 }"></div>

… but that feels like duplicating code. Fortunately, the ng-class directive supports having an expression passed in. What it evaluates to is what it will use as the class. So, I simply define it like this:

<div class="tile"
     ng-class="ctrl.bmiValue | bmi"
     title="Body Mass Index">...</div>

And ensure my CSS is set up correctly :

div.Obese {
    background: red;
} div.Overweight {
    background: lightcoral;
} div.Underweight {
    background: lightcoral;
}

Now I’m good to go. If you are concerned that the classes don’t follow convention (they are uppercase) and maybe need a more unique name (such as a prefix to avoid collisions) the solution is simple: add a parameter to the filter and based on that parameter, return the readable text or the translated class. Either way you encapsulate the logic in one place for reuse throughout your app.

Here’s a snap of the result scaled to a mobile form factor:

healthcalculator

If you are in the Atlanta area and reading this before October 11, 2014, please come join us at the code camp. It is a day loaded with great sessions. Until then, enjoy your Angular coding!

signature[1]

Friday, September 19, 2014

ALM with TFS: From the Drawing Board to the Cloud

I recently gave a lunch and learn at iVision about the features that Team Foundation Server provides for Application Lifecycle Management. I found that many companies still believe TFS is still only for source control, or have it installed on premise and aren't aware of the latest features. Even more important is the fact you can greatly reduce headaches and remove overhead by migrating your on-premise TFS installation to Visual Studio Online!

In this video I cover a brief overview of ALM and Agile, why I believe TFS helps bridge the gap between traditional waterfall process and new agile approaches, and finally what features are available including demos of the functionality in the cloud. I've had massive success using this tool on large, distributed projects so I look forward to any comments or questions you may have.

The summary:

Managing the lifecycle of software development can be a daunting task, especially after having adopted an Agile methodology that has you moving faster than ever. That is why it is more important than ever to have the right tools in place that allow you to effectively manage all facets of your SDLC from requirements gathering to testing and deployment. In the suite of tools available in the space of Application Lifecycle Management (ALM), Team Foundation Server (TFS) is a stand out. Let us show you how your organization can benefit from the advanced capabilities and unique configurability of TFS to successfully deliver your software development projects on time and on budget.

Here is the deck:




And you can view the video below:



Jeremy Likness
The posts on this blog are my own and do not represent the opinions, views, strategies, nor endorsement by iVision, Inc.