A couple years back, while beefing up on my front-end repertoire, I came across this nifty little front-end framework known as AngularJS. And had a fair bit of fun with it.
Angular 2 has come out since then, but for old times' sake, let's revisit one of the little projects I tinkered with in AngularJS. It's a BMI calculator I made for shits and giggles.
What's BMI?
It's an acronym for
Body-Mass Index. Basically, you take your height and weight, then apply a formula that gives you the BMI. The BMI chart tells you whether you're dangerously underweight, overweight, or just right, or any range in between.
Let's get started!
For this, you'll need a HTML file,
index.html, and a js file,
main.js
The
main.js file should be stored in the
js folder. This is not
absolutely necessary, but it's a good habit which we should follow.
Your HTML begins like this...
index.html
<!DOCTYPE html>
<html>
<head>
<title>BMI</title>
<script src="js/main.js"></script>
</head>
<body>
</body>
</html>
Now we're going to add a little something that makes this an Angular app - the reference to the external AngularJS library.
index.html
<!DOCTYPE html>
<html>
<head>
<title>BMI</title>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="js/main.js"></script>
</head>
<body>
</body>
</html>
Next, we add two range inputs with ids of
rngHeight and
rngWeight respectively, as well as labels for those inputs.
index.html
<!DOCTYPE html>
<html>
<head>
<title>BMI</title>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="js/main.js"></script>
</head>
<body>
<div>
<label for="rngHeight">Height </label>
<input type="range" id="rngHeight">
<br />
<label for="rngWeight">Weight </label>
<input type="range" id="rngWeight">
</div>
</body>
</html>
Let's set
rngHeight's input range between 1 to 300 cm, and
rngWeight's range between 1 to 300000 g. Which is probably a reasonable estimate.
index.html
<!DOCTYPE html>
<html>
<head>
<title>BMI</title>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="js/main.js"></script>
</head>
<body>
<div>
<label for="rngHeight">Height </label>
<input type="range" id="rngHeight" min="1" max="300">
<br />
<label for="rngWeight">Weight </label>
<input type="range" id="rngWeight" min="1" max="30000">
</div>
</body>
</html>
So this is what your current output looks like...
The front-end will do us, for now. Let's focus on adding functionality. To do this the Angular way, we need to modify the HTML this way.
index.html
<body ng-app="bmiApp">
And then we need to add this code into
main.js. This declares a variable, app, and sets it to a module created by AngularJS's
module() method, using the the element we just tagged with
ng-app="bmiApp" above. That means anything within the body tag is now considered part of the
app module.
main.js
var app = angular.module("bmiApp", []);
Now make these changes to your HTML. The div element that houses the range inputs
rngHeight and
rngWeight now has a new attribute
ng-controller with the value "bmiCtrl". This tags it as the controller
bmiCtrl. The
rngHeight input has
ng-model added as an attribute with the value "height_cm". Something similar has been done for
rngWeight.
Why will become clear momentarily.
index.html
<div ng-controller="bmiCtrl">
<label for="rngHeight">Height </label>
<input type="range" id="rngHeight" min="1" max="300" ng-model="height_cm">
<br />
<label for="rngWeight">Weight </label>
<input type="range" id="rngWeight" min="1" max="300000" ng-model="weight_g">
</div>
Now, let's add this code in. This creates a controller in the
app module, and passes in the string "bmiCtrl" as the first argument, and a function (with
$scope as the argument) as the second argument.
main.js
var app = angular.module("bmiApp", []);
app.controller("bmiCtrl",
function($scope)
{
}
);
Now add this code into the function. This ensures that the default values for the local scope variables
height_cm and
weight_g are 100 and 10000 respectively.
main.js
var app = angular.module("bmiApp", []);
app.controller("bmiCtrl",
function($scope)
{
$scope.height_cm=100;
$scope.weight_g=10000;
}
);
Refresh. Since we've bound
rngHeight to
height_cm and
rngWeight to
weight_g, it should look like this now.
OK, great! Now that data looks correct, let's add a little visual indicator to the range inputs. Having the values in centimeters and grams is necessary for functionality, but a
little too granular for human consumption. So we're going to display values in meters and kilograms.
index.html
<div ng-controller="bmiCtrl">
<label for="rngHeight">Height </label>
<input type="range" id="rngHeight" min="1" max="300" ng-model="height_cm">{{height_m}} m
<br />
<label for="rngWeight">Weight </label>
<input type="range" id="rngWeight" min="1" max="300000" ng-model="weight_g">{{weight_kg}} kg
</div>
Your display now has "m" and "kg" next to the range inputs. You'll notice that the double curly bracers and stuff don't show up - that's because the curlies are AngularJS's placeholders for values. And since the values
height_m and
weight_kg are currently undefined, they simply don't show up.
One more thing we need to add to your HTML.
ng-init ensures that the
calc_bmi() function is run when the page is initialized.
index.html
<div ng-controller="bmiCtrl" ng-init="calc_bmi()">
<label for="rngHeight">Height </label>
<input type="range" id="rngHeight" min="1" max="300" ng-model="height_cm">{{height_m}} m
<br />
<label for="rngWeight">Weight </label>
<input type="range" id="rngWeight" min="1" max="300000" ng-model="weight_g">{{weight_kg}} kg
</div>
Now let's do this. Here, within the scope defined by the controller
bmiCtrl, we have the
calc_cmi() function. First, it takes the values for
height_cm and
weight_g, and converts them using the functions
convert_height() and
convert_weight() respectively.
main.js
app.controller("bmiCtrl", function($scope)
{
$scope.calc_bmi=
function()
{
$scope.height_m=convert_height($scope.height_cm);
$scope.weight_kg=convert_weight($scope.weight_g);
};
$scope.height_cm=100;
$scope.weight_g=10000;
});
And here we define the functions
convert_height() and
convert_weight(). These functions are not accessible by the app, and run only within the scope of
calc_bmi.
convert_height() converts the value of its argument to meters and returns the new value to two decimal places.
convert weight() does something similar, except this time it converts grams to kilograms.
main.js
app.controller("bmiCtrl", function($scope)
{
$scope.calc_bmi=
function()
{
function convert_height(ht)
{
return (ht/100).toFixed(2);
}
function convert_weight(wt)
{
return (wt/1000).toFixed(2);
}
$scope.height_m=convert_height($scope.height_cm);
$scope.weight_kg=convert_weight($scope.weight_g);
};
$scope.height_cm=100;
$scope.weight_g=10000;
});
Now the values for
height_m and
weight_kg should show up!
OK, now we're going to make this little app react to your input. This ensures that
calc_bmi() is run not just on page initialization, but also whenever the values in the range inputs change!
index.html
<div ng-controller="bmiCtrl" ng-init="calc_bmi()">
<label for="rngHeight">Height </label>
<input type="range" id="rngHeight" min="1" max="300" ng-change="calc_bmi()" ng-model="height_cm">{{height_m}} m
<br />
<label for="rngWeight">Weight </label>
<input type="range" id="rngWeight" min="1" max="300000" ng-change="calc_bmi()" ng-model="weight_g">{{weight_kg}} kg
</div>
Refresh. Do the values change when you move the sliders?
Right. From here on, it's just a matter of further applying what we've already done! Modify your HTML.
index.html
<div ng-controller="bmiCtrl" ng-init="calc_bmi()">
<label for="rngHeight">Height </label>
<input type="range" id="rngHeight" min="1" max="300" ng-change="calc_bmi()" ng-model="height_cm">{{height_m}} m
<br />
<label for="rngWeight">Weight </label>
<input type="range" id="rngWeight" min="1" max="300000" ng-change="calc_bmi()" ng-model="weight_g">{{weight_kg}} kg
<h1>Your BMI is {{bmi}}</h1>
</div>
Again, you should not be seeing anything within the curly bracers, because those values are undefined.
Modify the JavaScript. This new line ensures that
height_m and
weight_kg are converted to BMI after being defined by converting
height_cm and
weight_g.
main.js
app.controller("bmiCtrl", function($scope)
{
$scope.calc_bmi=
function()
{
function convert_height(ht)
{
return (ht/100).toFixed(2);
}
function convert_weight(wt)
{
return (wt/1000).toFixed(2);
}
$scope.height_m=convert_height($scope.height_cm);
$scope.weight_kg=convert_weight($scope.weight_g);
$scope.bmi=convert_bmi($scope.height_m,$scope.weight_kg);
};
$scope.height_cm=100;
$scope.weight_g=10000;
});
And of course, here we define the
convert_bmi() function. It basically takes the height and weight in meters and kilograms respectively, and applies the formula, then returns the result to two decimal places.
main.js
app.controller("bmiCtrl", function($scope)
{
$scope.calc_bmi=
function()
{
function convert_height(ht)
{
return (ht/100).toFixed(2);
}
function convert_weight(wt)
{
return (wt/1000).toFixed(2);
}
function convert_bmi(ht,wt)
{
return (wt/(ht*ht)).toFixed(2);
}
$scope.height_m=convert_height($scope.height_cm);
$scope.weight_kg=convert_weight($scope.weight_g);
$scope.bmi=convert_bmi($scope.height_m,$scope.weight_kg);
};
$scope.height_cm=100;
$scope.weight_g=10000;
});
Now try your code again! The BMI calculation should appear, and should adjust with your sliders!
But hold on, we're not done yet. The BMI's just a number. The typical user, of course, wants to know what it means! So add this to your HTML.
index.html
<div ng-controller="bmiCtrl" ng-init="calc_bmi()">
<label for="rngHeight">Height </label>
<input type="range" id="rngHeight" min="1" max="300" ng-change="calc_bmi()" ng-model="height_cm">{{height_m}} m
<br />
<label for="rngWeight">Weight </label>
<input type="range" id="rngWeight" min="1" max="300000" ng-change="calc_bmi()" ng-model="weight_g">{{weight_kg}} kg
<h1>Your BMI is {{bmi}}</h1>
<p>{{remarks}}</p>
</div>
This creates a new scope variable,
remarks, which is derived from running the function
convert_remarks() with
bmi as an argument.
main.js
app.controller("bmiCtrl", function($scope)
{
function convert_height(ht)
{
return (ht/100).toFixed(2);
}
function convert_weight(wt)
{
return (wt/1000).toFixed(2);
}
function convert_bmi(ht,wt)
{
return (wt/(ht*ht)).toFixed(2);
}
$scope.calc_bmi=
function()
{
$scope.height_m=convert_height($scope.height_cm);
$scope.weight_kg=convert_weight($scope.weight_g);
$scope.bmi=convert_bmi($scope.height_m,$scope.weight_kg);
$scope.remarks=convert_remarks($scope.bmi);
};
$scope.height_cm=100;
$scope.weight_g=10000;
});
And of course,
convert_remarks() takes
bmi and returns a text message based on its range!
main.js
app.controller("bmiCtrl", function($scope)
{
function convert_height(ht)
{
return (ht/100).toFixed(2);
}
function convert_weight(wt)
{
return (wt/1000).toFixed(2);
}
function convert_bmi(ht,wt)
{
return (wt/(ht*ht)).toFixed(2);
}
function convert_remarks(bmi)
{
if (bmi<=20) return "You're malnourished.";
if (bmi>20&&bmi<=22) return "Looking a tad lightweight here.";
if (bmi>22&&bmi<=24) return "Looking good.";
if (bmi>24&&bmi<=27) return "Time to shed a few pounds.";
if (bmi>27) return "Dude, you have a problem. A HEAVY problem.";
}
$scope.calc_bmi=
function()
{
$scope.height_m=convert_height($scope.height_cm);
$scope.weight_kg=convert_weight($scope.weight_g);
$scope.bmi=convert_bmi($scope.height_m,$scope.weight_kg);
$scope.remarks=convert_remarks($scope.bmi);
};
$scope.height_cm=100;
$scope.weight_g=10000;
});
Re-run your code. The comments should appear now!
That's it for our little AngularJS project...
There's a whole lot more we can accomplish with this framework, of course. This wasn't anything I couldn't have done in plain old vanilla JavaScript, but it did feature two-way data binding and scoped functions and variables. Which makes this little app far more maintainable if I ever wanted to expand it. This little appetizer might actually motivate me to get off my arse and learn Angular2.
Is there more? Weight and see!
T___T