<!DOCTYPE html>
<html ng-app="geocode">

  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
    <title>Address Lookup/Geocode Service</title>
    <link href="//code.ionicframework.com/1.1.0/css/ionic.css" rel="stylesheet" />
    <link rel="stylesheet" href="style.css" />
    
    <script src="//code.ionicframework.com/1.1.0/js/ionic.bundle.js"></script>
    <script src="//maps.googleapis.com/maps/api/js?sensor=false"></script>
    <script data-require="lodash.js@3.10.0" data-semver="3.10.0" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.js"></script>
    <script data-require="angular-google-maps@2.0.11" data-semver="2.0.11" src="//rawgit.com/angular-ui/angular-google-maps/master/dist/angular-google-maps.min.js"></script>
    <script data-require="angular-simple-logger@*" data-semver="0.0.2" src="https://rawgit.com/nmccready/angular-simple-logger/master/dist/angular-simple-logger.js"></script>
    
    <script src="script.js"></script>
    <script src="modals.service.js"></script>
    <script src="geocode.service.js"></script>
    
  </head>

  <body ng-controller="GeocodeCtrl as vm">
    <ion-header-bar class="bar-positive">
      <h1 class="title">Geocode Service</h1>
    </ion-header-bar>
    <ion-content class="has-header">
      <div class="row responsive-sm">
        <div class="col col-offset-25 col-50">
      <div class="list card">
        <div class="item item-divider item-positive">Address Lookup</div>
        <div class="item">
          <div class="item-input-inset no-padding">
            <div class="item-input-wrapper no-padding">
              <input type="text" ng-model="vm.address" placeholder="Enter Address" ng-attr-clear-field="vm.isBrowser" />
            </div>
            <button id="address-lookup-search" ng-click="vm.on.testGeocode( vm.address )" class="button button-balanced button-small no-padding">
              <i ng-hide="vm.loading" class="icon ion-map"></i>
              <ion-spinner ng-show="vm.loading" class="spinner-light"></ion-spinner>
            </button>
          </div>
        </div>
        <div class="item">
          <label class="item item-input">
            <span class="input-label">Location</span>
            <input type="text" ng-model="vm.latlon" placeholder="[lat,lng]" readonly="readonly" />
          </label>
          <label class="item item-input">
            <span class="input-label">Formatted Address</span>
            <input type="text" ng-model="vm.addressFormatted" readonly="readonly" />
          </label>
        </div>
      </div>
      <div class="card">
        <div class="item item-divider item-assertive">console</div>
        <div class="item">
          <p ng-repeat="msg in vm.console">{{msg}}</p>
        </div>
      </div>
      </div></div>
    </ion-content>
  </body>

</html>
// condensed John Papa style
(function() {

  'use strict';
  
  angular.module('geocode', ['ionic', 'geocode.core']);
  angular.module('geocode.core', ['geocode.components']);
  angular.module('geocode.components', ['uiGmapgoogle-maps']);


  var appRun, ionicConfig, toastrConfig;

  appRun = function($rootScope, $ionicPlatform, $ionicHistory, $location, $state) {
    $ionicPlatform.ready(function() {
      if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
        cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      }
      if (window.StatusBar) {
        return StatusBar.styleLightContent();
      }
    });
  };


  ionicConfig = function($ionicConfigProvider) {
    $ionicConfigProvider.backButton.text('').icon('ion-ios-arrow-back').previousTitleText(false);
  };


  appRun.$inject = ['$rootScope', '$ionicPlatform', '$ionicHistory', '$location', '$state'];

  ionicConfig.$inject = ['$ionicConfigProvider'];

  angular.module('geocode.core')
    .config(ionicConfig)
    .run(appRun);
  

  var GeocodeCtrl;

  GeocodeCtrl = function($log, $scope, geocodeSvc) {
    var vm;
    vm = this;
    
    vm.address = "New York City";
    vm.latlon = "";
    vm.addressFormatted = "";
    vm.isBrowser = ionic.Platform.isWebView() === false
    vm.on = {
      testGeocode: function(address) {
        return geocodeSvc.getLatLon(address)
        .then(function(result) {
          vm.addressFormatted = result.address;
          vm.latlon = result.location
          console.log(["testGeocode", result]);
        });
      },
    }
    
    window.console.log = function(msg){
      vm.console.unshift(vm.console.length + ":" + JSON.stringify(msg, null, 2));
    }
    
    vm.loading = false;
    vm.console = [];
    console.log("ready")
    return vm
    
  };

  GeocodeCtrl.$inject = ['$log', '$scope', 'geocodeSvc'];

  angular.module('geocode')
    .controller('GeocodeCtrl', GeocodeCtrl);

}).call(this);


/* Styles go here */
#address-lookup-modal .item-floating-label input, #address-lookup-modal .item-input-inset input {
  width: 100%; }

#map .wrap {
  height: 360px; }
  @media (max-width: 567px) {
    #map .wrap {
      height: 200px; } }
  #map .wrap .angular-google-map-container {
    /*width: 360px;*/
    height: 360px; }
    @media (max-width: 567px) {
      #map .wrap .angular-google-map-container {
        height: 200px; } }

input {
  width: 100%;
}
# Address Lookup/Geocode Service for ionic Framework
This service uses `angular-google-maps` and the `Google Maps Geocoding API` to geocode an address string, and returns a formatted address and `[lat, lon]` value.

The service presents the geocode result in an $ionicModal and allows the user to 
1. customize the formatted address and 
1. drag the marker to correct the actual location.

If the geocode results include multiple locations, the top 5 results are displayed and the user can `click` to select the most accurate.

`bower.json` dependencies
```json
{
  "devDependencies": {
    "angular-google-maps": "~2.1.6",
    "ionic": "driftyco/ionic-bower#1.1.0",
    "lodash": "~3.10.1",
  }
}
```
(function() {
  'use strict';

  /*
   * @description: reusable $ionicModal service
   * see: http://forum.ionicframework.com/t/ionic-modal-service-with-extras/15357
   * also: http://codepen.io/anon/pen/KdzawK?editors=101
   * usage: appModalSvc.show( <templateUrl>, "controller as vm", params )
   */
  var ReusableModal;

  ReusableModal = function($ionicModal, $rootScope, $q, $injector, $controller) {
    var _cleanup, _evalController, show;
    show = function(templateUrl, controller, parameters, options) {
      var ctrlInstance, defaultOptions, dfd, modalScope, thisScopeId;
      dfd = $q.defer();
      modalScope = $rootScope.$new();
      thisScopeId = modalScope.$id;
      ctrlInstance = null;
      defaultOptions = {
        animation: 'slide-in-up',
        focusFirstInput: false,
        backdropClickToClose: true,
        hardwareBackButtonClose: true,
        modalCallback: null
      };
      options = angular.extend(defaultOptions, options, {
        scope: modalScope
      });
      $ionicModal.fromTemplateUrl(templateUrl, options).then(function(modal) {
        var ctrlEval, locals, same;
        modalScope.modal = modal;
        modalScope.openModal = function() {
          return modalScope.modal.show();
        };
        modalScope.closeModal = function(result) {
          dfd.resolve(result);
          return modalScope.modal.hide();
        };
        modalScope.$on('modal.hidden', function(thisModal) {
          var modalScopeId;
          if (thisModal.currentScope) {
            modalScopeId = thisModal.currentScope.$id;
            if (thisScopeId === thisModal.currentScope.$id) {
              dfd.resolve(null);
              return _cleanup(thisModal.currentScope);
            }
          }
        });
        if (angular.isObject(controller)) {
          modalScope.vm = ctrlInstance = controller;
          same = modalScope.vm === modalScope.modal.scope.vm;
          ctrlInstance['openModal'] = modalScope.openModal;
          ctrlInstance['closeModal'] = modalScope.closeModal;
        } else {
          locals = {
            '$scope': modalScope,
            'parameters': parameters
          };
          ctrlEval = _evalController(controller);
          if (ctrlEval.controllerName) {
            ctrlInstance = $controller(controller, locals);
          }
          if (ctrlEval.isControllerAs && ctrlInstance) {
            ctrlInstance['openModal'] = modalScope.openModal;
            ctrlInstance['closeModal'] = modalScope.closeModal;
          }
        }
        if (parameters != null) {
          angular.extend(modalScope, parameters);
        }
        return modalScope.modal.show().then(function() {
          modalScope.$broadcast('modal.afterShow', modalScope.modal);
          return typeof options.modalCallback === "function" ? options.modalCallback(modal) : void 0;
        });
      }, function(err) {
        return dfd.reject(err);
      });
      return dfd.promise;
    };
    _cleanup = function(scope) {
      scope.$destroy();
      if (scope.modal) {
        scope.modal.remove();
      }
    };
    _evalController = function(ctrlName) {
      var fragments, result;
      if (ctrlName == null) {
        ctrlName = '';
      }
      result = {
        isControllerAs: false,
        controllerName: '',
        propName: ''
      };
      fragments = ctrlName.trim().split(/\s+/);
      result.isControllerAs = fragments.length === 3 && (fragments[1] || '').toLowerCase() === 'as';
      if (result.isControllerAs) {
        result.controllerName = fragments[0];
        result.propName = ctrlName;
      } else {
        result.controllerName = ctrlName;
      }
      return result;
    };
    return {
      show: show
    };
  };

  ReusableModal.$inject = ['$ionicModal', '$rootScope', '$q', '$injector', '$controller'];

  angular.module('geocode.components')
    .factory('appModalSvc', ReusableModal);

}).call(this);
(function() {
  'use strict';
  var ClearFieldDirective, GEOCODER, Geocoder, MODAL_VIEW, VerifyLookupCtrl, geocodeSvcConfig;

  GEOCODER = {
    STATUS: {},
    instance: null,
    ZERO_RESULT_LOC: [37.77493, -122.419416]
  };

  MODAL_VIEW = {
    DISPLAY_LIMIT: 5,
    OFFSET_HEIGHT: 420 + 106, // 420 px for normal usage, +106px for plnkr toolbar
    GRID_RESPONSIVE_SM_BREAK: 680,
    MARGIN_TOP_BOTTOM: 0.1 + 0.1,
    MAP_MIN_HEIGHT: 200,
    MESSAGE: {
      ZERO_RESULTS_ERROR: "No results found, please try again.",
      VERIFY_LABEL: "This is how the location will be displayed",
      MULTIPLE_RESULTS: "[multiple results]"
    }
  };

  geocodeSvcConfig = function(uiGmapGoogleMapApiProvider, API_KEY) {
    var cfg;
    cfg = {};
    if (API_KEY) {
      cfg.key = API_KEY;
    }
    uiGmapGoogleMapApiProvider.configure(cfg);
  };

  geocodeSvcConfig.$inject = ['uiGmapGoogleMapApiProvider', 'API_KEY'];


  /*
   * @description Google Maps Geocode Service v3
   * see https://developers.google.com/maps/documentation/geocoding/intro
   */

  Geocoder = function($q, $ionicPlatform, appModalSvc, uiGmapGoogleMapApi) {
    var init, mathRound6, self;
    init = function(maps) {
      GEOCODER.STATUS = maps.GeocoderStatus;
      GEOCODER.instance = new maps.Geocoder();
    };
    mathRound6 = function(v) {
      if (_.isNumber(v)) {
        return Math.round(v * 1000000) / 1000000;
      }
      return v;
    };
    uiGmapGoogleMapApi.then(function(maps) {
      console.log("uiGmapGoogleMapApi promise resolved");
      init(maps);
    });
    self = {

      /*
      @description an Entry Point for this service, returns an object with a geocode location
      @return object { address: location: place_id:(options) }
        'NOT FOUND', 'CANCELED', 'ERROR'
       */
      getLatLon: function(address) {
        return self.displayGeocode(address).then(function(result) {
          var location, ref, ref1, ref2, resp;
          if (!result || result === 'CANCELED') {
            return null;
          }
          if (_.isString(result)) {
            return result;
          }
          if ((ref = result.override) != null ? ref.location : void 0) {
            location = (ref1 = result.override) != null ? ref1.location : void 0;
          } else {
            location = result['geometry']['location'];
            location = [location.lat(), location.lng()];
          }
          location = _.map(location, function(v) {
            return mathRound6(v);
          });
          resp = {
            address: ((ref2 = result.override) != null ? ref2.address : void 0) || result['formatted_address'],
            location: location
          };
          if (!result.override) {
            resp['place_id'] = result['place_id'];
          }
          return resp;
        })["catch"](function(err) {
          return 'ERROR';
        });
      },

      /*
      @description launches addressMap modal to allow user to verifiy location of address
      @param address String
      @return object, one geocode result or CANCELED, ZERO_RESULTS
       */
      displayGeocode: function(address) {
        return self.geocode(address).then(function(results) {
          if (results === GEOCODER.STATUS.ZERO_RESULTS) {
            console.log("ZERO_RESULTS FOUND");
            results = [self.getPlaceholderDefault()];
          }
          return self.showResultsAsMap(address, results).then(function(result) {
            return result;
          });
        })["catch"](function(err) {
          console.warn(err);
        });
      },
      geocode: function(address) {
        var dfd;
        if (GEOCODER.instance == null) {
          return $q.reject("Geocoder JS lib not ready");
        }
        dfd = $q.defer();
        GEOCODER.instance.geocode({
          "address": address
        }, function(result, status) {
          switch (status) {
            case 'OK':
              return dfd.resolve(result);
            case GEOCODER.STATUS.ZERO_RESULTS:
              return dfd.resolve(GEOCODER.STATUS.ZERO_RESULTS);
            default:
              console.err(['geocodeSvc.geocode()', status]);
              return dfd.reject({
                status: status,
                result: result
              });
          }
        });
        return dfd.promise;
      },
      getPlaceholderDefault: function() {
        var geoCodeResult;
        return geoCodeResult = {
          geometry: {
            location: {
              lat: function() {
                return GEOCODER.ZERO_RESULT_LOC[0];
              },
              lng: function() {
                return GEOCODER.ZERO_RESULT_LOC[1];
              }
            }
          },
          status: GEOCODER.STATUS.ZERO_RESULTS,
          formatted_address: '[location not found]'
        };
      },
      showResultsAsMap: function(address, geoCodeResults) {
        return appModalSvc.show('address-lookup.template.html', 'VerifyLookupCtrl as vm', {
          address: address,
          geoCodeResults: geoCodeResults
        }).then(function(modalResult) {
          var geoCodeResult, mm;
          if (_.isString(modalResult || !modalResult)) {
            return modalResult;
          }
          mm = modalResult;
          geoCodeResult = mm['geoCodeResults'][0];
          geoCodeResult.override = {};
          if (mm['marker-moved']) {
            geoCodeResult.override['location'] = mm.location;
          }
          if (mm['address-changed']) {
            geoCodeResult.override['address'] = mm.addressDisplay;
          }
          return geoCodeResult;
        })["catch"](function(err) {
          return $q.reject(err);
        });
      },

      /*
       * Utility Methods
       */

      /*
       * @description get a location array from an object
       * @param point object of type
       *     GEOCODER.instance.geocode() result
       *     marker from ui-gmap-marker dragend event on map
       *     model from ui-gmap-markers click event
       *       {id: latitude: longititde: formatted_address:}
       * @return [lat,lon] round to 6 decimals for google Maps API
       */
      getLocationFromObj: function(point) {
        var ref;
        if (point == null) {
          point = {};
        }
        if (((ref = point['geometry']) != null ? ref.location : void 0) != null) {
          return [mathRound6(point['geometry']['location'].lat()), mathRound6(point['geometry']['location'].lng())];
        }
        if (point.getPosition != null) {
          return [mathRound6(point.getPosition().lat()), mathRound6(point.getPosition().lng())];
        }
        if (point.longitude != null) {
          return [mathRound6(point.latitude), mathRound6(point.longitude)];
        }
        return null;
      },

      /*
       * @description add a 'random' offset to latlon to mask exact location
       * @param latlon Array, [lat,lon] expressed as decimal
       */
      maskLatLon: function(latlon, key) {
        var offset;
        key = key.slice(0, 11);
        offset = {
          lat: (self.a2nHash(latlon[0] + key) % 25) / 10000,
          lon: (self.a2nHash(latlon[1] + key) % 25) / 10000
        };
        return [mathRound6(latlon[0] + offset.lat), mathRound6(latlon[1] + offset.lon)];
      },

      /*
      @description: get google Map object for angular-google-maps,
        configured map places marker or circle at location
      @params: options
        id: string optional
        location: [lat,lon]  or [ [lat,lon], [lat,lon] ], render circle or marker at location
        type: [circle, marker]
        circleRadius: 500, in meters
        draggableMap: true
        draggableMarker: true
       */
      getMapConfig: function(options) {
        var gMapPoint, mapConfig, mapConfigOptions, markers;
        _.defaults(options, {
          location: [],
          markers: [],
          type: 'oneMarker',
          circleRadius: 500,
          draggableMap: true,
          draggableMarker: true
        });
        mapConfigOptions = {
          'map': {
            options: {
              draggable: options.draggableMap
            }
          }
        };
        switch (options.type) {
          case 'circle':
            gMapPoint = {
              latitude: mathRound6(options.location[0]),
              longitude: mathRound6(options.location[1])
            };
            mapConfigOptions['circle'] = {
              center: gMapPoint,
              stroke: {
                color: '#FF0000',
                weight: 1
              },
              radius: options.circleRadius,
              fill: {
                color: '#FF0000',
                opacity: '0.2'
              }
            };
            break;
          case 'oneMarker':
            gMapPoint = {
              latitude: mathRound6(options.location[0]),
              longitude: mathRound6(options.location[1])
            };
            mapConfigOptions['oneMarker'] = {
              idKey: '1',
              coords: gMapPoint,
              options: {
                draggable: options.draggableMarker
              }
            };
            if (options.draggableMarker) {
              mapConfigOptions['oneMarker']['events'] = {
                'dragend': options.dragendMarker
              };
            }
            break;
          case 'manyMarkers':
            markers = _.map(options.markers, function(result, i, l) {
              var point;
              point = result['geometry']['location'];
              return {
                'id': i,
                'latitude': mathRound6(point.lat()),
                'longitude': mathRound6(point.lng()),
                'formatted_address': result.formatted_address
              };
            });
            mapConfigOptions['manyMarkers'] = {
              models: markers,
              options: {
                draggable: options.draggableMarker
              },
              events: {
                'click': options.clickMarker
              }
            };
            if (options.draggableMarker) {
              mapConfigOptions['manyMarkers']['events']['dragend'] = options.dragendMarker;
            }
            gMapPoint = markers[0];
        }
        return mapConfig = {
          type: options.type,
          center: angular.copy(gMapPoint),
          zoom: 14,
          scrollwheel: false,
          options: mapConfigOptions
        };
      }
    };
    return self;
  };

  Geocoder.$inject = ['$q', '$ionicPlatform', 'appModalSvc', 'uiGmapGoogleMapApi'];


  /*
  @description Controller for geocodeSvc.showResultsAsMap() Modal
  @param parameters.geoCodeResult Array of geocode results
         parameters.address String, the original search string
   */

  VerifyLookupCtrl = function($scope, parameters, $q, $timeout, $window, geocodeSvc) {
    var init, parseLocation, setMapHeight, setupMap, vm;
    vm = this;
    vm.isBrowser = !ionic.Platform.isWebView();
    vm.MESSAGE = MODAL_VIEW.MESSAGE;
    vm.isValidMarker = function() {
      if (vm['error-address0']) {
        return false;
      }
      if (vm.map.type === 'oneMarker') {
        return true;
      }
      return false;
    };
    init = function(parameters) {
      var stop;
      vm['geoCodeResults'] = parameters.geoCodeResults.slice(0, MODAL_VIEW.DISPLAY_LIMIT);
      vm['map'] = setupMap(parameters.address, vm['geoCodeResults']);
      stop = $scope.$on('modal.afterShow', function(ev) {
        var h;
        h = setMapHeight();
        if (typeof stop === "function") {
          stop();
        }
      });
    };
    setMapHeight = function() {
      var contentH, mapH, styleH;
      contentH = $window.innerWidth <= MODAL_VIEW.GRID_RESPONSIVE_SM_BREAK ? $window.innerHeight : $window.innerHeight * (1 - MODAL_VIEW.MARGIN_TOP_BOTTOM);
      mapH = contentH - MODAL_VIEW.OFFSET_HEIGHT;
      mapH = Math.max(MODAL_VIEW.MAP_MIN_HEIGHT, mapH);
      // console.log(["height=", $window.innerHeight, contentH, mapH]);
      styleH = "#address-lookup-map .wrap {height: %height%px;}\n#address-lookup-map .angular-google-map-container {height: %height%px;}";
      styleH = styleH.replace(/%height%/g, mapH);
      angular.element(document.getElementById('address-lookup-style')).append(styleH);
      return mapH;
    };
    parseLocation = function(geoCodeResultOrModel, target) {
      var location0, resp;
      if (_.isEmpty(geoCodeResultOrModel)) {
        return {};
      }
      location0 = geocodeSvc.getLocationFromObj(geoCodeResultOrModel);
      resp = {
        'location': location0,
        'latlon': location0.join(', '),
        'addressFormatted': geoCodeResultOrModel.formatted_address,
        'addressDisplay': angular.copy(geoCodeResultOrModel.formatted_address),
        'error-address0': null
      };
      switch (geoCodeResultOrModel.status) {
        case GEOCODER.STATUS.ZERO_RESULTS:
          resp['error-address0'] = vm.MESSAGE.ZERO_RESULTS_ERROR;
          resp['latlon'] = null;
          resp['addressDisplay'] = null;
      }
      _.extend(target, resp);
      return resp;
    };
    setupMap = function(address, geoCodeResults, model) {
      var isZeroResult, mapConfig, mapOptions, markerCount, selectedLocation;
      if (isZeroResult = geoCodeResults === GEOCODER.STATUS.ZERO_RESULTS) {
        geoCodeResults = [geocodeSvc.getPlaceholderDefault()];
      }
      vm['address0'] = address;
      markerCount = model != null ? 1 : geoCodeResults.length;
      if (markerCount === 0) {
        return;
      }
      if (markerCount === 1) {
        selectedLocation = model || vm['geoCodeResults'][0];
        parseLocation(selectedLocation, vm);
        vm['marker-moved'] = false;
        mapOptions = {
          type: isZeroResult ? 'none' : 'oneMarker',
          location: vm['location'],
          draggableMarker: true,
          dragendMarker: function(marker, eventName, args) {
            vm['location'] = geocodeSvc.getLocationFromObj(marker);
            vm['latlon'] = vm['location'].join(', ');
            vm['marker-moved'] = true;
          }
        };
        mapConfig = geocodeSvc.getMapConfig(mapOptions);
        return mapConfig;
      }
      vm['latlon'] = null;
      vm['addressFormatted'] = vm.MESSAGE.MULTIPLE_RESULTS;
      vm['addressDisplay'] = '';
      vm['error-address0'] = null;
      mapOptions = {
        type: 'manyMarkers',
        draggableMarker: true,
        markers: geoCodeResults,
        clickMarker: function(marker, eventName, model) {
          var index, newMapConfig;
          index = model.id;
          vm['geoCodeResults'] = [vm['geoCodeResults'][index]];
          newMapConfig = setupMap(model.formatted_address, null, model);
          vm['address-changed'] = true;
          vm['marker-moved'] = true;
          vm['map'] = newMapConfig;
        },
        dragendMarker: function(marker, eventName, model) {
          mapOptions.clickMarker(marker, eventName, model);
        }
      };
      return geocodeSvc.getMapConfig(mapOptions);
    };
    vm.updateGeocode = function(address) {
      vm.loading = true;
      return geocodeSvc.geocode(address).then(function(results) {
        if (results === GEOCODER.STATUS.ZERO_RESULTS) {
          console.log("ZERO_RESULTS FOUND");
          results = [geocodeSvc.getPlaceholderDefault()];
        }
        return results;
      }).then(function(results) {
        var newMapConfig;
        vm['geoCodeResults'] = results;
        newMapConfig = setupMap(address, vm['geoCodeResults']);
        vm['address-changed'] = false;
        vm['map'] = newMapConfig;
      }, function(err) {
        return $q.reject(err);
      })["finally"](function() {
        return $timeout(function() {
          return vm.loading = false;
        }, 250);
      });
    };
    $scope.$watch('vm.addressDisplay', function(newV) {
      vm['address-changed'] = true;
    });
    init(parameters);
    return vm;
  };

  VerifyLookupCtrl.$inject = ['$scope', 'parameters', '$q', '$timeout', '$window', 'geocodeSvc'];

  ClearFieldDirective = function($compile, $timeout) {
    var directive;
    directive = {
      restrict: 'A',
      require: 'ngModel',
      scope: {},
      link: function(scope, element, attrs, ngModel) {
        var btnTemplate, inputTypes, template;
        inputTypes = /text|search|tel|url|email|password/i;
        if (element[0].nodeName !== 'INPUT') {
          throw new Error("clearField is limited to input elements");
        }
        if (!inputTypes.test(attrs.type)) {
          throw new Error("Invalid input type for clearField" + attrs.type);
        }
        btnTemplate = "<i ng-show=\"enabled\" ng-click=\"clear()\" class=\"icon ion-close pull-right\">&nbsp;</i>";
        template = $compile(btnTemplate)(scope);
        element.after(template);
        scope.clear = function() {
          ngModel.$setViewValue(null);
          ngModel.$render();
          scope.enabled = false;
          return $timeout(function() {
            return element[0].focus();
          }, 150);
        };
        element.bind('focus', function(e) {
          scope.enabled = !ngModel.$isEmpty(element.val());
          scope.$apply();
        });
      }
    };
    return directive;
  };

  ClearFieldDirective.$inject = ['$compile', '$timeout'];


  angular.module('geocode.components')
    .constant('API_KEY', GEOCODER.API_KEY)
    .config(geocodeSvcConfig)
    .factory('geocodeSvc', Geocoder)
    .directive('clearField', ClearFieldDirective)
    .controller('VerifyLookupCtrl', VerifyLookupCtrl);

}).call(this);

<style id="address-lookup-style">
  @media (min-width: 680px) {
    #address-lookup-modal-view.modal { top: 10%; bottom: 10%;}
  }
  #address-lookup-search { min-width:32.5px; } 
  #address-lookup-search svg {width: 26px; height: 26px; margin: 5px 0;}
  #address-lookup-map .wrap {min-height: 200px;}
  #address-lookup-map .angular-google-map-container {min-height: 200px;}
</style>
<ion-modal-view id="address-lookup-modal-view">
  <ion-header-bar class="bar-balanced"> 
    <h1 class="title">Address Lookup</h1>
  </ion-header-bar>
  <ion-content>
    <div id="address-lookup-modal" class="list condensed">
      <div class="item">
        <div class="item-input-inset no-padding">
          <div class="item-input-wrapper no-padding">
            <input type="text" ng-model="vm.address0" placeholder="Enter Address" ng-attr-clear-field="vm.isBrowser"/>
          </div>
          <button id="address-lookup-search" ng-click="vm.updateGeocode(vm.address0)" class="button button-balanced button-small no-padding"><i ng-hide="vm.loading" class="icon ion-search"></i>
            <ion-spinner ng-show="vm.loading" class="spinner-light"></ion-spinner>
          </button>
        </div>
        <div ng-show="vm['error-address0']" class="error"><span class="assertive">{{vm['error-address0']}}</span></div>
      </div>
      <label class="item item-floating-label"><span class="label">Location</span>
        <input type="text" ng-model="vm.latlon" readonly="readonly"/>
      </label>
      <label class="item item-floating-label"><span class="label">Formatted Address</span>
        <input type="text" ng-model="vm.addressFormatted" readonly="readonly"/>
      </label>
      <div class="item item-floating-label"><span class="label">Display Address</span>
        <div class="item-input-inset no-padding">
          <div class="item-input-wrapper no-padding">
            <input type="text" ng-model="vm.addressDisplay" ng-attr-clear-field="vm.isBrowser"/>
          </div>
        </div>
        <p class="help"><span ng-show="vm.isValidMarker()" class="positive">{{vm.MESSAGE.VERIFY_LABEL}}</span></p>
      </div>
      <div id="address-lookup-map" class="item item-complex">
        <div ng-if="vm.map" class="wrap">{{opt = vm.map.options;''}}
          <ui-gmap-google-map center="vm.map.center" zoom="vm.map.zoom" options="opt.map.options">
            <ui-gmap-circle ng-if="vm.map.type=='circle'" center="opt.circle.center" radius="opt.circle.radius" stroke="opt.circle.stroke"></ui-gmap-circle>
            <ui-gmap-marker ng-if="vm.isValidMarker()" idKey="opt.oneMarker.idKey" coords="opt.oneMarker.coords" options="opt.oneMarker.options" events="opt.oneMarker.events"></ui-gmap-marker>
            <ui-gmap-markers ng-if="vm.map.type=='manyMarkers'" fit="true" idkey="id" coords="'self'" models="opt.manyMarkers.models" options="opt.manyMarkers.options" modelbyref="true" xxclick="'opt.manyMarkers.events.click'" events="opt.manyMarkers.events"></ui-gmap-markers>
          </ui-gmap-google-map>
        </div>
        <p class="padding-horizontal"> <span ng-show="vm.isValidMarker()" class="padding-horizontal positive">Drag the marker to change location</span><span ng-show="vm.map.type=='manyMarkers'" class="padding-horizontal positive">Multiple locations found, click Marker to select</span></p>
      </div>
      <div class="item">
        <div class="button-bar">
          <button ng-click="closeModal('CANCELED')" class="button button-balanced button-outline">Cancel</button>
          <button ng-click="closeModal(vm)" ng-disabled="vm.isValidMarker()==false" class="button button-balanced">OK</button>
        </div>
      </div>
    </div>
  </ion-content>
</ion-modal-view>