Create A Complex Calculator App Using Ionic Framework

If you’ve been keeping up, I previously wrote two tutorials on the topic of evaluating mathematical expressions. The first tutorial was on the topic of converting an Infix expression into a Postfix expression also known as Reverse Polish Notation using the Shunting Yard algorithm. The second tutorial was on the topic of solving the Postfix expression.

Using the hybrid application framework, Ionic Framework, we’re going to create a calculator application for solving these complex expressions. Think of it as a mobile front end for the nice algorithms we made.

As a refresher, we’ll remember that the Infix expression 5 + 3 * 6 - ( 5 / 3 ) + 7 will first be converted into the Postfix expression 5 3 6 * + 5 3 / - 7 + before finally solving.

Let’s start by creating a fresh Ionic Android and iOS project:

ionic start CalculatorApp blank
cd CalculatorApp
ionic platform add ios
ionic platform add android

It is important to note that if you’re not using a Mac computer, you cannot add and build for the iOS platform.

At this point, I’m going to assume you’ve read my other articles regarding Infix and Postfix Notation. Inside your project’s www/js/app.js file, let’s create the following AngularJS factory:

calculatorApp.factory("$calculatorFactory", function() {

    return {

        infixToPostfix: function(infix) {
            var outputQueue = "";
            var operatorStack = [];
            var operators = {
                "^": {
                    precedence: 4,
                    associativity: "Right"
                },
                "/": {
                    precedence: 3,
                    associativity: "Left"
                },
                "*": {
                    precedence: 3,
                    associativity: "Left"
                },
                "+": {
                    precedence: 2,
                    associativity: "Left"
                },
                "-": {
                    precedence: 2,
                    associativity: "Left"
                }
            }
            infix = infix.replace(/\s+/g, "");
            infix = infix.split(/([\+\-\*\/\^\(\)])/).clean();
            for(var i = 0; i < infix.length; i++) {
                var token = infix[i];
                if(token.isNumeric()) {
                    outputQueue += token + " ";
                } else if("^*/+-".indexOf(token) !== -1) {
                    var o1 = token;
                    var o2 = operatorStack[operatorStack.length - 1];
                    while("^*/+-".indexOf(o2) !== -1 && ((operators[o1].associativity === "Left" && operators[o1].precedence <= operators[o2].precedence) || (operators[o1].associativity === "Right" && operators[o1].precedence < operators[o2].precedence))) {
                        outputQueue += operatorStack.pop() + " ";
                        o2 = operatorStack[operatorStack.length - 1];
                    }
                    operatorStack.push(o1);
                } else if(token === "(") {
                    operatorStack.push(token);
                } else if(token === ")") {
                    while(operatorStack[operatorStack.length - 1] !== "(") {
                        outputQueue += operatorStack.pop() + " ";
                    }
                    operatorStack.pop();
                }
            }
            while(operatorStack.length > 0) {
                outputQueue += operatorStack.pop() + " ";
            }
            return outputQueue;
        },

        solvePostfix: function(postfix) {
            var resultStack = [];
            postfix = postfix.split(" ");
            for(var i = 0; i < postfix.length; i++) {
                if(postfix[i].isNumeric()) {
                    resultStack.push(postfix[i]);
                } else {
                    var a = resultStack.pop();
                    var b = resultStack.pop();
                    if(postfix[i] === "+") {
                        resultStack.push(parseInt(a) + parseInt(b));
                    } else if(postfix[i] === "-") {
                        resultStack.push(parseInt(b) - parseInt(a));
                    } else if(postfix[i] === "*") {
                        resultStack.push(parseInt(a) * parseInt(b));
                    } else if(postfix[i] === "/") {
                        resultStack.push(parseInt(b) / parseInt(a));
                    } else if(postfix[i] === "^") {
                        resultStack.push(Math.pow(parseInt(b), parseInt(a)));
                    }
                }
            }
            if(resultStack.length > 1) {
                return "error";
            } else {
                return resultStack.pop();
            }
        }

    }

});

Don’t let the above $calculatorFactory throw you off. If you read my articles, you’ll know it isn’t as bad as it seems. We also need to add the following prototypes. Add them to the bottom of your www/js/app.js file because they are necessary when using the $calculatorFactory we added.

String.prototype.isNumeric = function() {
    return !isNaN(parseFloat(this)) && isFinite(this);
}

Array.prototype.clean = function() {
    for(var i = 0; i < this.length; i++) {
        if(this[i] === "") {
            this.splice(i, 1);
        }
    }
    return this;
}

Now that we brushed up on all the old stuff, it is time to continue on creating our application.

Let’s worry about the UI of our app first. I apologize for my poor design skills ahead of time, but I’m sure you’ll get the idea. In fact, if you’re a long time follower of my blog, you’ll probably recognize a lot of the following from my previous article regarding a pin code interface in Ionic Framework. In your www/index.html file, add the following code:

<ion-content ng-controller="CalculatorController" class="center-vertical">
    <div class="list list-inset">
        <label class="item item-input">
            <input ng-model="expression" type="text" placeholder="Math Goes Here" />
        </label>
    </div>
    <div style="margin-top: 50px">
      <div class="row">
          <div class="col col-20 col-offset-10">
              <button class="button button-light button-stretch" ng-click="add(1)">1</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add(2)">2</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add(3)">3</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add('-')">-</button>
          </div>
      </div>
      <div class="row">
          <div class="col col-20 col-offset-10">
              <button class="button button-light button-stretch" ng-click="add(4)">4</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add(5)">5</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add(6)">6</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add('+')">+</button>
          </div>
      </div>
      <div class="row">
          <div class="col col-20 col-offset-10">
              <button class="button button-light button-stretch" ng-click="add(7)">7</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add(8)">8</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add(9)">9</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add('/')">/</button>
          </div>
      </div>
      <div class="row">
          <div class="col col-20 col-offset-10">
              <button class="button button-light button-stretch"></button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add(0)">0</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="calculate(expression)">=</button>
          </div>
          <div class="col col-20">
              <button class="button button-light button-stretch" ng-click="add('*')">*</button>
          </div>
      </div>
  </div>
</ion-content>

Beyond all the AngularJS components you’ll notice the class="center-vertical". That is custom and is the following in our www/css/style.css file:

.center-vertical {
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-box-direction: normal;
    -moz-box-direction: normal;
    -webkit-box-orient: horizontal;
    -moz-box-orient: horizontal;
    -webkit-flex-direction: row;
    -ms-flex-direction: row;
    flex-direction: row;
    -webkit-flex-wrap: nowrap;
    -ms-flex-wrap: nowrap;
    flex-wrap: nowrap;
    -webkit-box-pack: center;
    -moz-box-pack: center;
    -webkit-justify-content: center;
    -ms-flex-pack: center;
    justify-content: center;
    -webkit-align-content: stretch;
    -ms-flex-line-pack: stretch;
    align-content: stretch;
    -webkit-box-align: center;
    -moz-box-align: center;
    -webkit-align-items: center;
    -ms-flex-align: center;
    align-items: center;
}

.center-vertical > * {
    -webkit-box-ordinal-group: 1;
    -moz-box-ordinal-group: 1;
    -webkit-order: 0;
    -ms-flex-order: 0;
    order: 0;
    -webkit-box-flex: 1;
    -moz-box-flex: 1;
    -webkit-flex: 1 1 auto;
    -ms-flex: 1 1 auto;
    flex: 1 1 auto;
    -webkit-align-self: auto;
    -ms-flex-item-align: auto;
    align-self: auto;
}

With all the UI stuff added, we end up with a product that looks like the following:

Ionic Calculator

Pretty crude right? Now it is time to take care of all the AngularJS stuff that resides in our front-end UI. We’ll be creating a controller called CalculatorController which contains an add(value) and calculate(expression) function.

calculatorApp.controller("CalculatorController", function($scope, $calculatorFactory) {

    $scope.calculate = function(expression) {
        var postfix = $calculatorFactory.infixToPostfix(expression);
        $scope.expression = $calculatorFactory.solvePostfix(postfix.trim());
    }

    $scope.add = function(value) {
        if($scope.expression === "" || $scope.expression === undefined) {
            $scope.expression = value;
        } else {
            $scope.expression = $scope.expression + " " + value;
        }
    }

});

The above code follows a very simple concept. The add(value) function will add the button values to the expression box which also accepts free-type. When you hit the equals button, the calculate(expression) is triggered solving the equation. The equation is first converted into Postfix and then the solution is posted back into the expression box.

Conclusion

Creating the mobile application was not a complex process. We built upon two algorithms for solving mathematical expressions and then built a mobile front end around them. This front end consisted of pretty much only a grid of buttons that were centered vertically on the screen. In the end, we ended up with a calculator that could solve beyond just the simple one operator math equation.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in Java, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Apache Cordova. Nic writes about his development experiences related to making web and mobile development easier to understand.

Search

Follow Us

Subscribe

Subscribe to my newsletter for monthly tips and tricks on subjects such as mobile, web, and game development.

Subscribe on YouTube

Support This Site