Today I presented at MinneWebCon on the University of Minnesota campus. I gave an introduction to AngularJS called “Getting Super with Angular JS”.
To run the application files, you will need Node and Grunt installed on your system.
I’ve been trying to devise a solution to working with mock data in the AngularJS. I’m not a huge fan of the solutions out there because too much debugging logic to determine mock or real data gets put into the code that may end up in production unless stripped out. Many of them are also limited to either having all services mocked, or none. This is difficult to manage when services are being developed by the back end team at the same time as the UI. We needed a quick way to turn specific services on and off on the fly. I think I’ve come up with a pretty nice solution.
The code below was picked apart from the application we’re building, modified with new variable names and had some business logic stripped out, so forgive me if there are some syntax or coding errors – it should be clean enough to get the idea though. I’ve left it in Coffeescript to save space.
To start with, I build a value for service configurations that sets up our end point URLs for both real services, and mock. It also directs the logic to the specific JSON files to return when a mock services is requested.
"use strict" angular.module("Bakery.config").value "$serviceConfig", url: "http://localhost:9000/mock_data/" mockUrl: "http://localhost:9000/mock_data/" useMock: false donuts: call: "donuts" mock: "/food/donuts.json" useMock: false cakes: call: "cakes" mock: "/food/cakes.json" useMock: true
Next we setup the actual services:
"use strict" angular.module("Bakery.services", ["ngResource"]).factory "$bakeryService", ($http, $serviceConfig) -> doCall : (serviceCall, call, args ) -> if $serviceConfig.useMock or call.useMock return $http.get $serviceConfig.mockUrl + call.mock else return serviceCall.apply this, args donuts : -> return @doCall( @_donuts, $serviceConfig.donuts, Array.prototype.slice.call(arguments) ) cakes : -> return @doCall( @_cakes, $serviceConfig.cakes, Array.prototype.slice.call(arguments) ) _donuts : (isFrosted, isFilled) -> console.log start call = $serviceConfig.containers url = $serviceConfig.url + call config = params: frosted: isFrosted isFilled: isFilled $http.get url, config _cakes : () -> call = $serviceConfig.configurations url = $serviceConfig.url + call $http.get url
Now we have a $bakeryService that will look at our configuration to determine if we want to use mock data or pull from the actual service. We can inject this to other areas of the application and make calls easily without having to think about whether or not we want to use mock or service data – it should just work.
"use strict" angular.module("GenesisApp.registeredViews").directive "query", ($bakeryService) -> restrict: "EAC" controller: ($scope) -> $bakeryService.donuts(true, false).then ((response) -> # success $scope.donuts = response.data ), (response) -> # failure console.log "Error getting donuts" $bakeryService.cake().then ((response) -> # success $scope.cakes = response.data ), (response) -> # failure console.log "Error getting cakes"
I like this approach because it limits the amount of extra code for mock services to just a few lines. The doCall method handles the decisions as to whether or not mock data should be used, and passes on the arguments to the real call when needed. The mock data information can be completely removed from the $serviceConfig object when its sent to production, limiting exposure to your mock services setup.
Another benefit is one can set the services to use mock data for all services or individually. This is helpful when services and UI logic are being built at the same time and some services aren’t completely functional or break. Just switch that particular’s service config to useMock: true and you can keep on programming.