Understanding Custom AngularJS Promises, Services, and Punching Clowns with a Phonegap Example

If you look at the documentation for $q, the AngularJS promise library inspired by Q, you'll notice that it functions a bit different than the promise returned by $http.

For example:

// => myPromise standard $q promise returned or created above somewhere
myPromise.then(function() {
  // => success code here
}, function() {
  // => failure code here
})

vs $http promise:

$http.get(/* ... */)
.success(function() {
  // => success
}).failure(function() {
  // => failure
})

The documentation does not make it clear why they're different. But this StackOverflow post has some details. Arguably, it can be said that it may be a bit cleaner. Let's take a look on you can leverage this idea to make a clean modal confirmation service for a Phonegap/Cordova app.

Phonegap/Cordova

As you may know, Adobe Phonegap provides notifications so that your app can have native alert dialogs instead of the dialogs created by JavaScript.

so, instead of:

// => returns `true` if 'OK' was clicked and `false` if 'Cancel' was clicked
window.confirm('Are you sure that you want to punch the clown?')

you can do this:

var onConfirm = function(idx) {
  if (idx === 1) {
    // => 'OK' was pressed (clown punched)
  } else {
    // => 'Cancel' was pressed (you're a weeny)
  }
}

navigator.notification.confirm('Are you sure that you want to punch the clown?', onConfirm)

even better, you can add a title and change the text of the buttons:

navigator.notification.confirm('Are you sure that you want to punch the clown?', onConfirm, 'Punch Clown?', ['Punch Clown', 'Cancel'])

AngularJS Services

Let's wrap this bad boy up in a service so that we don't have to worry if we're testing our desktop browser or native mobile app.

alerts.js:

var alertsModule = angular.module('alerts', [])

alertsModule.service('alerts', function($q, $window) {
  this.confirm = function(message, title, buttonLabels, successCallback, cancelCallback) {
    if (navigator.notification && navigator.notification.confirm) { //native app
      var onConfirm = function(idx) {
        idx === 1 ? successCallback() : cancelCallback()
      }

      navigator.notification.confirm(message, onConfirm, title, buttonLabels)
    } else {
      $window.confirm(message) ? successCallback() : cancelCallback()
    }
  }
})

now somewhere in your app, you can do:

function onSuccess(){
  // => bozo punched
}

function onCancel() {
  // => ya, Bozo scares me too, not punched.
}

alerts.confirm('Do you want to punch bozo?', 'Punch him?', ['Punch Bozo', "No, I'm a Weeny"], onSuccess, onCancel)

but that's ugly.

Promises

I won't talk about the virtues of promises, as it's been done ad nausaem. I rarely use them, but when I do... probably a Dos Equis meme here.

Let's modify our service to have a promise:

mod.service('alerts', function($q, $window) {
  this.confirm = function(message, title, buttonLabels) {
    var defer = $q.defer();

    if (navigator.notification && navigator.notification.confirm) {
      var onConfirm = function(idx) {
        idx === 1 ? defer.resolve() : defer.reject()
      }

      navigator.notification.confirm(message, onConfirm, title, buttonLabels)
    } else {
      $window.confirm(message) ? defer.resolve() : defer.reject()
    }

    return defer.promise
  }
})

now you can do this:

alerts.confirm('Do you want to punch bozo?', 'Punch him?', ['Punch Bozo', "No, I'm a Weeny"])
.then(function(){
  // => bozo punched
}, function() {
  // => ya, Bozo scares me too, not punched.
})

Custom Promise

You can make a custom promise to add clarity, let's modify our alerts service again:

mod.service('alerts', function($q, $window) {
  this.confirm = function(/* ... */) {
    /* ... */
    return alertPromise(defer.promise)
  }
})

function alertPromise(promise) {
  promise.ok = function(fn) {
    promise.then(function() {
      fn.apply(this, arguments)
    })
    return promise
  }

  promise.cancel = function(fn) {
    promise.then(null, function() {
      fn.apply(this, arguments)
    })
    return promise
  }

  return promise
}

now you can do this:

alerts.confirm('Do you want to punch bozo?', 'Punch him?', ['Punch Bozo', "No, I'm a Weeny"])
.ok(function(){
  // => bozo punched
})
.cancel(function() {
  // => ya, Bozo scares me too, not punched.
})

is this better? Not really. But at least your clown punching code has a little more clarity.

If you made it this far, you should follow me on Twitter.

-JP

Want to test-drive Bitcoin without any risk? Check out my bitcoin wallet Coinbolt. It includes test coins for free.

comments powered by Disqus