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 :
[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 :
[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 :
[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 :
[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] :
[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
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?
LikeLike
LikeLike
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
LikeLike
1. I don’t know if that works . Check out this : https://firebase.google.com/docs/database/security/
2. You can set an additional variable and check the availability in the client side.
3. I am currently busy. So I wont be able to update the app with payment module.
LikeLike
Good job! I have a question, if I want to just show items from cat01 in menu2, how could I do this?
LikeLike
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.
LikeLike
Thanks for your fast response! I’ll look to learn and if I find a way similar to sql I share it with you.
LikeLike
Am I missing anything ?
haven’t been able to get the product(s) to the cart for some reason
The DB file is limited to Categories/Items only
where are the rest ?
LikeLike
Have you set the rules for DB. I am pretty much sure that, this tutorial https://www.youtube.com/watch?v=GKlnjdbmPxU covered the entire instructions, to make it functional.
Yes, the DB file is limited to Categories/items only. This was meant to be an ionic starter. You are free to modify it.
LikeLike
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
LikeLike
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+
LikeLike
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).
LikeLike
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
LikeLike
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
LikeLike
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. ?
LikeLike
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
LikeLike
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
LikeLike
angular 2 please
LikeLike
do u have iconic app builder?? can you please send?? mshfarzeel@yahoo.com @@yenyan:disqus
LikeLike
Hello Sir,
i need iconic app builder full crack version. can you please send me?
LikeLike
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?
LikeLike
i have 17k items. app is so slowly . what can i do. can u help me
LikeLike
As per the requests, I have created an Ionic2 project for the same.
https://github.com/arjunsk/ionic2_firebase_shopping_cart
Star the project ——> to support the project
Subscribe to my channel ——> for early access to updates
Follow me on Github ——> for more such awesome projects.
LikeLike