Ionic Firebase Shopping Cart: Part 2

This is my 2nd tutorial in the tutorial series, “Getting Started with Ionic Firebase“. This is a continuation of the Part 1, where i implemented Login, Register and Profile sections of FoodKart V0.3. If you find it useful, then please make sure to subscribe and follow me on Git for the latest updates and releases.

[wpi_designer_button text=’Download’ link=’https://github.com/arjunsk/ionic-firebase-shopping-cart’ style_id=’48’ icon=’github’ target=’_blank’]

I this tutorial i will discussing on how, i implemented Grid Menu, Cart and Orders sections. Feel free to contribute to this project to end up, having your name in the about section of our app.

So lets start cooking!

Menu :

Screenshot_2016-07-10-13-39-15

[color-box]  menu2.html [The Food Menu Page]  [/color-box]

<ion-view style="" class=" " id="page7" title="Menu">
<!-- loadMenu() is called to load the menu array  -->
    <ion-content class="has-header" padding="false"  ng-init="loadMenu()"  >


	<!-- $index is the iterating variable
	 To access individual elements of the array we use menu[$index]
	 Each row contains two columns. We use $index % 2 === 0 so that 
	 we only add 1 row when the $index is incremented by 2, rather than adding 2 rows. 
	-->
		<div class="row" ng-repeat="item in menu" ng-if="$index % 2 === 0" style="border-top: 1px solid #999;">


			<!-- Column 1-->
			<!-- We use $index to 1st column elements -->
			<div class="col col-50 " ng-if="$index < menu.length" style="padding:1px;border-right:1px solid #999" >
				  <div class="item item-image" style="border-width: 0px; border-style: none;">
					<img ng-click="showProductInfo(menu[$index].$id)" ng-src="{{'img/fk/'+ menu[$index].image +'.jpg'}}" width="100%" />
				  </div>
				  <a class="item item-icon-right assertive" href="#"  style="border-width: 0px; border-style: none;" >
					<p ng-click="showProductInfo(menu[$index].$id)" style="font-size: 12px;text-align:left;">{{menu[$index].name}} <br/>
						<span style="font-weight: bold;color: black;">₹ {{menu[$index].price}}</span>
					</p>
					<i style="font-size: 25px; margin-right:0px;right:0px;"  ng-click="addToCart(menu[$index])" class="icon ion-ios-cart"></i>
				  </a>
			</div>

			<!-- Column 2-->
			<!-- We use $index+1 to access 2nd column elements -->
			<div class="col col-50 " ng-if="$index+1  < menu.length" style="padding:1px;">
				  <div class="item item-image" style="border-width: 0px; border-style: none;">
					<img ng-click="showProductInfo(menu[$index+1].$id)" ng-src="{{'img/fk/'+ menu[$index+1].image +'.jpg'}}" width="100%" />
				  </div>
				  <a class="item item-icon-right assertive" href="#"  style="border-width: 0px; border-style: none;" >
					<p ng-click="showProductInfo(menu[$index+1].$id)" style="font-size: 12px;text-align:left;">{{menu[$index+1].name}} <br/>
						<span style="font-weight: bold;color: black;">₹ {{menu[$index+1].price}}</span>
					</p>
					<i style="font-size: 25px;right: 0px;"  ng-click="addToCart(menu[$index+1])" class="icon ion-ios-cart"></i>
				  </a>
			</div>

		</div>


	</ion-content>
</ion-view>

[color-box]  controller.js [ menu2Ctrl ]  [/color-box]

/*
SharedCartService: handles the cart functions
*/

.controller('menu2Ctrl', function($scope,$rootScope,$ionicSideMenuDelegate,fireBaseData,$state,
                                  $ionicHistory,$firebaseArray,sharedCartService,sharedUtils) {

  //Check if user already logged in
  firebase.auth().onAuthStateChanged(function(user) {
    if (user) {
      $scope.user_info=user; //Saves data to user_info. This is used to add menu item to a respective user_id
    }else {

      $ionicSideMenuDelegate.toggleLeft(); //To close the side bar
      $ionicSideMenuDelegate.canDragContent(false);  // To remove the sidemenu white space

      $ionicHistory.nextViewOptions({
        historyRoot: true
      });
      $rootScope.extras = false;
      sharedUtils.hideLoading();
      $state.go('tabsController.login', {}, {location: "replace"});

    }
  });

  // On Loggin in to menu page, the sideMenu drag state is set to true
  $ionicSideMenuDelegate.canDragContent(true);
  $rootScope.extras=true;

  // When user visits A-> B -> C -> A and clicks back, he will close the app instead of back linking
  $scope.$on('$ionicView.enter', function(ev) {
    if(ev.targetScope !== $scope){
      $ionicHistory.clearHistory();
      $ionicHistory.clearCache();
    }
  });



  $scope.loadMenu = function() {
    sharedUtils.showLoading();
    $scope.menu=$firebaseArray(fireBaseData.refMenu());
    sharedUtils.hideLoading();
  }

  $scope.showProductInfo=function (id) {

  };
  $scope.addToCart=function(item){
    sharedCartService.add(item);
  };

})

[color-box]  services.js [ sharedCartService ]  [/color-box]

 .factory('sharedCartService', ['$ionicPopup','fireBaseData','$firebaseArray',function($ionicPopup, fireBaseData, $firebaseArray){

    var uid ;// uid is temporary user_id

    var cart={}; // the main Object to be returned


    //Check if user already logged in
    firebase.auth().onAuthStateChanged(function(user) {
      if (user) {
        uid=user.uid;
        cart.cart_items = $firebaseArray(fireBaseData.refCart().child(uid));
      }
    });




    //Add to Cart
    cart.add = function(item) {
      //check if item is already added or not
      fireBaseData.refCart().child(uid).once("value", function(snapshot) {

        if( snapshot.hasChild(item.$id) == true ){

          //if item is already in the cart
          var currentQty = snapshot.child(item.$id).val().item_qty;

          fireBaseData.refCart().child(uid).child(item.$id).update({   // update
            item_qty : currentQty+1
          });

        }else{

          //if item is new in the cart
          fireBaseData.refCart().child(uid).child(item.$id).set({    // set
            item_name: item.name,
            item_image: item.image,
            item_price: item.price,
            item_qty: 1
          });
        }
      });
    };

    cart.drop=function(item_id){
      fireBaseData.refCart().child(uid).child(item_id).remove();
    };

    cart.increment=function(item_id){

      //check if item is exist in the cart or not
      fireBaseData.refCart().child(uid).once("value", function(snapshot) {
        if( snapshot.hasChild(item_id) == true ){

          var currentQty = snapshot.child(item_id).val().item_qty;
          //check if currentQty+1 is less than available stock
          fireBaseData.refCart().child(uid).child(item_id).update({
            item_qty : currentQty+1
          });

        }else{
          //pop error
        }
      });

    };

    cart.decrement=function(item_id){

      //check if item is exist in the cart or not
      fireBaseData.refCart().child(uid).once("value", function(snapshot) {
        if( snapshot.hasChild(item_id) == true ){

          var currentQty = snapshot.child(item_id).val().item_qty;

          if( currentQty-1 <= 0){
            cart.drop(item_id);
          }else{
            fireBaseData.refCart().child(uid).child(item_id).update({
              item_qty : currentQty-1
            });
          }

        }else{
          //pop error
        }
      });

    };

    return cart;
  }])

In the Part 1 of this series, we have seen a cart icon on the navigation bar and also the Sliding Menu Drawer. We had created a additional controller ( indexCtrl ) for manipulating those sections. Now we will see the indexCtrl in detail.

Index :

index_html

[color-box]  controller.js [ indexCtrl  ]  [/color-box] :

// Accesing the $rootScope variable extras
.controller('indexCtrl', function($scope,$rootScope,sharedUtils,$ionicHistory,$state,$ionicSideMenuDelegate,sharedCartService) {

    //Check if user already logged in
    firebase.auth().onAuthStateChanged(function(user) {
      if (user) {
        $scope.user_info=user; //Saves data to user_info

        //Only when the user is logged in, the cart qty is shown
        //Else it will show unwanted console error till we get the user object
        $scope.get_total= function() {
          var total_qty=0;
          for (var i = 0; i < sharedCartService.cart_items.length; i++) {
            total_qty += sharedCartService.cart_items[i].item_qty;
          }
          return total_qty;
        };

      }else {

		// If the user is not logged in
        $ionicSideMenuDelegate.toggleLeft(); //To close the side bar
        $ionicSideMenuDelegate.canDragContent(false);  // To remove the sidemenu white space

        $ionicHistory.nextViewOptions({
          historyRoot: true
        });
        $rootScope.extras = false;
        sharedUtils.hideLoading();
        $state.go('tabsController.login', {}, {location: "replace"});

      }
    });

    $scope.logout=function(){

      sharedUtils.showLoading();

      // Main Firebase logout
      firebase.auth().signOut().then(function() {


        $ionicSideMenuDelegate.toggleLeft(); //To close the side bar
        $ionicSideMenuDelegate.canDragContent(false);  // To remove the sidemenu white space

        $ionicHistory.nextViewOptions({
          historyRoot: true
        });


        $rootScope.extras = false;
        sharedUtils.hideLoading();
        $state.go('tabsController.login', {}, {location: "replace"});

      }, function(error) {
         sharedUtils.showAlert("Error","Logout Failed")
      });

    }

  })

Cart :

Screenshot_2016-07-10-13-39-25

[color-box]  myCart.html [ Users Shopping Cart ]  [/color-box]

<ion-view style="" class=" " id="page9" title="My Cart">
    <ion-content class="has-header" padding="true">

      <div>
        <p>Total ( {{get_qty()}} ) =
          <strong> ₹ {{total_amount}}</strong>
        </p>
      </div>
      <button ng-click="checkout()" class="button button-assertive button-block ">Check Out</button>

      <div ng-repeat="item in cart"   >
        <div class="list card" >
          <div style="min-height: 30px;" class="item item-divider">{{item.item_name}} </div>
          <div class="item item-body" >

            <img ng-src="{{'img/fk/'+ item.item_image +'.jpg'}}" width="100%" height="auto" style="box-shadow: none;">
            <form class="list"></form>
            <div>
              <p>{{item.item_qty}} x ₹ {{item.item_price}} =
                <strong> ₹ {{ item.item_qty *  item.item_price  }}</strong>
              </p>
            </div>
            <div class="button-bar" style="box-shadow: none;">
              <button ng-click="removeFromCart(item.$id)" class="button button-assertive button-block button-small icon ion-android-delete" ></button>
              <button class="button button-stable button-clear button-block button-small icon ion-plus" ng-click="inc(item.$id)"></button>
              <button class="button button-stable button-clear button-block button-small">{{item.item_qty}}</button>
              <button class="button button-stable button-clear button-block  icon ion-minus" ng-click="dec(item.$id)" ></button>
            </div>
          </div>
        </div>
      </div>



    </ion-content>
</ion-view>

[color-box]  controllers.js [The container]  [/color-box]

.controller('myCartCtrl', function($scope,$rootScope,$state,sharedCartService) {

    $rootScope.extras=true;

    //Check if user already logged in
    firebase.auth().onAuthStateChanged(function(user) {
      if (user) {

        $scope.cart=sharedCartService.cart_items;  // Loads users cart

		// get_qty function
        $scope.get_qty = function() {
          $scope.total_qty=0;
          $scope.total_amount=0;

          for (var i = 0; i < sharedCartService.cart_items.length; i++) {
            $scope.total_qty += sharedCartService.cart_items[i].item_qty;
            $scope.total_amount += (sharedCartService.cart_items[i].item_qty * sharedCartService.cart_items[i].item_price);
          }
          return $scope.total_qty;
        };
      }
      //We dont need the else part because indexCtrl takes care of it
    });

    $scope.removeFromCart=function(c_id){
      sharedCartService.drop(c_id);
    };

    $scope.inc=function(c_id){
      sharedCartService.increment(c_id);
    };

    $scope.dec=function(c_id){
      sharedCartService.decrement(c_id);
    };

    $scope.checkout=function(){
      $state.go('checkout', {}, {location: "replace"});
    };

})

Check Out :

Screenshot_2016-07-10-13-39-35

[color-box]  checkout.html [ User checkout page ]  [/color-box]

<ion-view title="Checkout"  id="page16" >
  <ion-content padding="false" class="has-header">

    <form name="checkoutForm" class="list " ng-submit="pay(address_choice,pay_choice)" >

      <!--Address Header-->
      <label ng-click="addManipulation()" class="item item-input  item ic-selected" >
        <a class="icon icon-right ion-ios-location" style="margin-right: 10px;" ></a>
        Address
        <i  class="icon icon-right ion-android-add-circle" style="font-size: larger ;margin-left: 10px;"></i>
      </label>


      <!-- Address -->
      <ion-list ng-repeat="item in addresses" >
          <ion-radio name="addr_group" ng-value="item.$id" ng-model="$parent.address_choice">
            <a class="icon icon-right ion-ios-location"></a>
            {{item.nickname}} <br/>
            {{item.address}} <br/>
            Pin : {{item.pin}}<br/>
            Phone : {{item.phone}}
          </ion-radio>
      </ion-list>




      <!--Payment Header-->
      <label ng-click="addManipulation()" class="item item-input  item ic-selected" >
        <a class="icon icon-right ion-cash" style="margin-right: 10px;" ></a>
        Mode of Pay
      </label>


      <!-- Payment Options -->
      <ion-list  ng-repeat="item in payments">
          <ion-radio name="pay_group" ng-value="item.id" ng-model="$parent.pay_choice" > {{item.name}} </ion-radio>
      </ion-list>


      <button type="submit" class=" button button-calm  button-block icon-right ion-android-arrow-forward ">
        PAY
      </button>

    </form>

  </ion-content>


</ion-view>

[color-box] controller.js [ checkoutCtrl ]  [/color-box]

.controller('checkoutCtrl', function($scope,$rootScope,sharedUtils,$state,$firebaseArray,
                                     $ionicHistory,fireBaseData, $ionicPopup,sharedCartService) {

    $rootScope.extras=true;

    //Check if user already logged in
    firebase.auth().onAuthStateChanged(function(user) {
      if (user) {
        $scope.addresses= $firebaseArray( fireBaseData.refUser().child(user.uid).child("address") );
        $scope.user_info=user;
      }
    });

	// Contains all the payment mode. Haven't implemented this section 
    $scope.payments = [
      {id: 'CREDIT', name: 'Credit Card'},
      {id: 'NETBANK', name: 'Net Banking'},
      {id: 'COD', name: 'COD'}
    ];

    $scope.pay=function(address,payment){

      if(address==null || payment==null){
        //Check if the checkboxes are selected ?
        sharedUtils.showAlert("Error","Please choose from the Address and Payment Modes.")
      }
      else {
        // Iterated through all the cart item
        for (var i = 0; i < sharedCartService.cart_items.length; i++) {
          
		  //Add cart item to order table
          fireBaseData.refOrder().push({

            //Product data is hardcoded for simplicity
            product_name: sharedCartService.cart_items[i].item_name,
            product_price: sharedCartService.cart_items[i].item_price,
            product_image: sharedCartService.cart_items[i].item_image,
            product_id: sharedCartService.cart_items[i].$id,

            //item data
            item_qty: sharedCartService.cart_items[i].item_qty,

            //Order data
            user_id: $scope.user_info.uid,
            user_name:$scope.user_info.displayName,
            address_id: address,
            payment_id: payment,
            status: "Queued"
          });

        }

        //Remove users cart
        fireBaseData.refCart().child($scope.user_info.uid).remove();

        sharedUtils.showAlert("Info", "Order Successfull");

        // Go to past order page
        $ionicHistory.nextViewOptions({
          historyRoot: true
        });
        $state.go('lastOrders', {}, {location: "replace", reload: true});
      }
    }



    $scope.addManipulation = function(edit_val) {  // Takes care of address add and edit ie Address Manipulator


      if(edit_val!=null) {
        $scope.data = edit_val; // For editing address
        var title="Edit Address";
        var sub_title="Edit your address";
      }
      else {
        $scope.data = {};    // For adding new address
        var title="Add Address";
        var sub_title="Add your new address";
      }
      // An elaborate, custom popup
      var addressPopup = $ionicPopup.show({
        template: '<input type="text"   placeholder="Nick Name"  ng-model="data.nickname"> <br/> ' +
        '<input type="text"   placeholder="Address" ng-model="data.address"> <br/> ' +
        '<input type="number" placeholder="Pincode" ng-model="data.pin"> <br/> ' +
        '<input type="number" placeholder="Phone" ng-model="data.phone">',
        title: title,
        subTitle: sub_title,
        scope: $scope,
        buttons: [
          { text: 'Close' },
          {
            text: '<b>Save</b>',
            type: 'button-positive',
            onTap: function(e) {
              if (!$scope.data.nickname || !$scope.data.address || !$scope.data.pin || !$scope.data.phone ) {
                e.preventDefault(); //don't allow the user to close unless he enters full details
              } else {
                return $scope.data;
              }
            }
          }
        ]
      });

      addressPopup.then(function(res) {

        if(edit_val!=null) {
          //Update  address
          fireBaseData.refUser().child($scope.user_info.uid).child("address").child(edit_val.$id).update({    // set
            nickname: res.nickname,
            address: res.address,
            pin: res.pin,
            phone: res.phone
          });
        }else{
          //Add new address
          fireBaseData.refUser().child($scope.user_info.uid).child("address").push({    // set
            nickname: res.nickname,
            address: res.address,
            pin: res.pin,
            phone: res.phone
          });
        }

      });

    };


  })



Now finally the last section, ie the Order Status.

Last Orders [ Keeps track of Order status and Past orders] :

Screenshot_2016-07-10-13-39-43

[color-box]  lastOrders.html [ The order list ]  [/color-box]

<ion-view style="" class=" " id="page10" title="Orders">
    <ion-content class="has-header" padding="true">


      <ion-list ng-repeat="item in orders">

        <ion-item class="item-thumbnail-left" >

          <img  ng-src="{{'img/fk/'+ item.product_image +'.jpg'}}" >

          <a class="badge badge-balanced" style="position: absolute;right:0;" >{{item.status}}</a>

          <h4> {{item.product_name}} </h4>
          <p>Qty: {{item.item_qty}}  Price: ₹ {{item.product_price}}</p>


        </ion-item>

      </ion-list>


    </ion-content>
</ion-view>

[color-box]  controllers.js [ lastOrdersCtrl ]  [/color-box]

  .controller('lastOrdersCtrl', function($scope,$rootScope,fireBaseData,sharedUtils) {

    $rootScope.extras = true;
    sharedUtils.showLoading();

    //Check if user already logged in
    firebase.auth().onAuthStateChanged(function (user) {
      if (user) {
        $scope.user_info = user;

		// This is similar to SQL : where user_id="my_id" 
        fireBaseData.refOrder()
          .orderByChild('user_id')
          .startAt($scope.user_info.uid).endAt($scope.user_info.uid)
          .once('value', function (snapshot) {
            $scope.orders = snapshot.val();
            $scope.$apply();
          });
          sharedUtils.hideLoading();
      }
    });

})

This concludes the major section of FoodKart.

I have integrated crosswalk, improving the speed 3x. Will be discussing the technical tweaks in the next section.

Please make sure to subscribe and follow me on Git.

UPDATE 28-12-2016: (Video Tutorial)

UPDATE 05-11-2017: (Ported to Ionic2)

Project Link: github.com/arjunsk/ionic2_firebase_shopping_cart

 

23 thoughts on “Ionic Firebase Shopping Cart: Part 2

  1. Hello! I have installed your app. It works, but not correctly. In section “menu” there are no items. can you help me with this problem?

    Like

  2. This is a great tutorial and works like a charm…I have few questions like

    1. Why if the menu is not displayed if i set the permission as “Auth!null ” instead of true? it shows permission error if set it as mentioned above.

    2. How do i update the product availability into the firebase?

    3. Do you have any idea to integrate payment gateway with this app?

    Thanks with anticipation and once again great job guys

    Like

    1. Sorry, I haven’t implemented them yet. I will add those features in the future updates.

      PS: I bileave you have to retrieve the whole menu items from the database and filter them based on chosen category in the client side. We can’t use JOINS like in SQL.

      Like

      1. Thanks for your fast response! I’ll look to learn and if I find a way similar to sql I share it with you.

        Like

  3. Very Good joob, Thanks.

    Just one problem, the fonctions “createUserWithEmailAndPassword” and “signInWithEmailAndPassword” works with chrome but when i want to use it on my device I have the firebase error “Aut/network_request_failed” I’ve search the source of this error and i didn’t find it.

    Do you have any ideas ?
    Thanks A Lot

    Like

    1. I don’t have that issue on my phone. Just for your info, i have currently integrated crosswalk (ie chrome webveiw) into my android app. I am pretty much sure that, it worked without crosswalk. Also check if the app is getting the internet permission from android( in case it is Marshmallow, mine is Jelly Bean ). I haven’t checked it on Android 6+

      Like

    2. I don’t have that issue on my phone. Just for your info, i have currently integrated crosswalk (ie chrome webveiw) into my android app. I am pretty much sure that, it worked without crosswalk. Also check if the app is getting the internet permission from android( in case it is Marshmallow, mine is Jelly Bean).

      Like

      1. Yes i have integrate crosswalk and i have all the permission in my androidManifest.

        This error is very strange in fact sometimes the signin works sometimes not and exacty the same things with the login.

        Also when i want to create a user the error “auth/network-request-failed” appear but when i check the database,the user exist …

        At the beginnig i thougth that the form with the ng-submit and a button with the type=”submit” instead of the ng-click will be concluant …. but no.

        Strange

        Like

  4. Very nice tutorial. Just a pointer you should be handling the logic in angular services rather than controller. Otherwise its a really good tutorial.

    Thanks

    Like

  5. Very nice tutorial , but just one question , i was able to set it up with firebase and everything works fine, but how do i change the currency symbol for other countries. ?

    Like

  6. I have a problem while building your project.
    Here is the image link

    system information:

    cordova CLI: 6.5.0
    Ionic CLI Version: 2.2.2
    Ionic App Lib Version: 2.2.1
    ios-deploy version: Not installed
    ios-sim version: Not installed
    OS: Windows 10
    Node Version: v7.4.0
    Xcode version: Not installed
    Android API level 19 to 25
    Windows 10

    Please help ASAP

    Like

  7. I want user’s phone number in that “orders” directory but when i put in Line Number 48 “telephone: $firebaseArray( fireBaseData.refUser().child(user.uid).child(“telephone”) )” in controller.js (checkout) it says user ref not defined.
    please help
    i want this to work as shown in pic below

    Like

  8. If I want to add more data to the form that files should I play?
    That is, if what I want is to store the name, paternal and maternal surname, which one should I edit?

    Like

Leave a comment