In previous posts I have said that I would like to focus my engineering team on building the and not the stack. Infrastructue is something that a lot providers have solved the problem for and honestly I do feel that eCommerce is at a sweet spot and when I look at the prices of something like Shopify Plus, I get hosting, a platform, a CDN, preferential transaction rates, priority support, etc which at the end is worth significantly more than actually spending money on engineers to develop, maintain, and secure a site.

Thus far the only thing that I have found out that Shopify can’t do is to control custom HTTP headers and response codes such as 302’s or even 404’s. Sometimes a solution like this is needed but thus far I haven’t found a show stopper or something that I think I am patching with a very ghetto solution.

Using Angular to build the Experience

So, what I think is being smart about utilizing my resources, I decided to look into Angular to really focus on building the experience. Why Angular? well, Angular allows me to build a web site to behave more like building an app. What do I mean by that?

Well in technical terms that means using an MVC (Model-View-Controller)-like architecture where the logic is actually separated from the presentation, making the code more readable and maintainable. For those of you that have some experience using javascript, you will understand how easy it is to make a mess of the code.

Once I had made that decision, the first question is, will it work with Shopify?

Shopify and Angular 101

I decided that the first test would be replacing the AjaxifyCart written by Caroline Schnapp with an Angular controller. This gave me an excuse and a first project to get my feet wet on it.

Now knowing what I would be working on, I found my first roadblock: liquid.

Splitting design from functionality

Typically the HTML is plagued with classes, ids and nested functions that it rarely happens that when something is styled or cleaned up the code is not messed. When the HTML’s logic you are writing relies only on ng- attributes then splitting things becomes easier

Liquid vs Angular

So in 2 colliding systems that you use curly’s {{ }} to define page renderings, where does liquid start and where does the Angular templates begin?

One cool thing is that Angular it provides a way to override the “curly’s” that it uses. Using the code below, I changed my app to parse {[{}]}

1 var app = angular.module('ShopifyApp', []);
2 
3 app.config(['$interpolateProvider',function ($interpolateProvider)
4 {
5     $interpolateProvider.startSymbol('{[{').endSymbol('}]}');
6 }]);

Splitting things up

I want to treat this as much as possible like a mobile app. And if this was an Android or iOS project, I’d probably have different custom views to split the project, in this case I decided to split this into 3 components. Controllers have variables and methods that each controller needs, in iOS terms, this would be analogous to a View Controller and the HTML inside is the equivalent of a view.

Why did I split them into 3, because there are pages where there are multiple products so being able to store and pass the right variables seemed like a logical split. Disclaimer: Maybe I could have merged all three into one but I wanted to learn and understand more about Angular.

  • AddToCartController - The button that adds an item to the cart.
  • CartDetailPopupController - The popup that appears as a tooltip once you add an item to the cart.
  • CartTotalController - The one that displays the number of elements in the cart.

I also wrote a Service which were not part of the original design but I figured it was the only way to communicate multiple controllers.

  • cartItemService - A service that performs all the ajax actions and notifies all controllers that listen for events once an action is completed.

In the service, and in the spirit to split code from logic, I am wrapping all the HTTP calls I need to do and in Shopify, once you add to cart, you also have to query the cart to find out the total of elements so the service performs two http calls, one for finding out what’s in the cart, and another to add to the cart.

 1 app.service('cartItemService', ['$rootScope','$http', function($rootScope, $http) {
 2     var refreshCartData = function()
 3     {
 4         var self = this;
 5         $http.get('/cart.js').success(function(data){
 6             $rootScope.$broadcast('SHOPIFY_CART_UPDATED', data);
 7         });
 8     };
 9 
10     var addToCart = function(addItem)
11     {
12         var self = this;
13         $http.post('/cart/add.js', addItem).success(function(data){
14             self.refreshCartData();
15             $rootScope.$broadcast('SHOPIFY_CART_ADDED_ITEMS', data);
16         }).error(function(data, status, headers, config) {
17             $rootScope.$broadcast('SHOPIFY_CART_ADDED_ITEMS_ERROR', data);
18         });
19 
20     };
21 
22     return {
23         refreshCartData: refreshCartData,
24         addToCart: addToCart,
25     };
26 }]);

And in the spirit to keep the post shorter than the Harry Potter books, I am just going to post the code of one controller, once you understand this, I think the rest are very easy to deduce.

The “Add to cart” button, wherever I want to add it or place it looks like this:

1 <div ng-controller="AddToCartController" ng-click="addToCart()" ng-init="prodid="  ng-bind-html="label" class="add-to-cart-btn">Add to Cart</div>

And the controller has two main functions: One that is called to the ng-click event that essentially changes the label, and the other that will listen to the service sending an event. It is important to notice that when things are ready to add it it delegates it to the service then it will respond when the service notifies back. I have removed extra code that is irrelevant to depict the problem and solution.

 1 app.controller('AddToCartController', function ($scope, $http, cartItemService) {
 2     $scope.addToCart = function()
 3     {
 4         $scope.label = 'Adding...';
 5         var prodData = {"id":$scope.prodid,
 6                         "return_to":"back",
 7                         "quantity":1};
 8 
 9         cartItemService.addToCart(prodData);
10     };
11 
12     $scope.$on('SHOPIFY_CART_ADDED_ITEMS', function(response, args) {
13         if (args.id!==undefined && args.id!==$scope.prodid)
14         {
15             return;
16         }
17 
18         $scope.label = 'Thank you!!';
19 
20         // Reset to the original value
21         setTimeout(function(){
22             $scope.label = 'Add to Cart';
23             $scope.$apply();
24         }, 1000);
25     });
26 
27     $scope.label = 'Add to Cart';
28 
29 });

Conclusion

I do think that the code is significantly easier to read, troubleshoot, test and maintain. I have had a couple of instances that I have had extra requests and it just requires one or two extra line of code to fix the issue in a very clean way.