Tuesday, December 8, 2015

AngularJS: Building a Postal Lookup Directive using Google APIs

In this post, I will show you how to build an angular directive that can be used to validate and provide full US addresses to the view in which it is used. Let’s see in action first:

SNAGHTMLb452191

So on right side, I have a text box – as you start typing in the address, it uses Google’s geo-coding APIs behind the scene and uses the Angular Bootstrap - Type Ahead directive to display the suggestions. Once the address is selected, it shows the full Address and a “Copy Address” Button as shown below:

image

When a copy button is clicked, the directive calls a function that was provided with the directive with address object. The consumer view (left side above) and use that and display in the fields. So as you can imagine, the directive discussed here is just a helping user to improve experience of selecting an address.

Let’s understand the dependencies:

1. Google API should have been included:

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>



2. The Type-ahead depends on UI Bootstrap


3. All other Angular Plumbing should have been done, the purpose of this article is to show the angular directive code only


So my angular directive looks like this:


   1:  (function() {
   2:      'use strict';
   3:   
   4:      angular
   5:          .module('app')
   6:          .directive('stPostalLookup', stPostalLookup);
   7:   
   8:      stPostalLookup.$inject = ['$window'];
   9:      
  10:      function stPostalLookup ($window) {
  11:          // Usage:
  12:          //     <stPostalLookup></stPostalLookup>
  13:          // Creates:
  14:          // 
  15:          var directive = {
  16:              link: link,
  17:              restrict: 'EA',
  18:              controller: 'stPostalLookupController',
  19:              scope:{
  20:                  addressCallback: "&"
  22:              },
  23:              //controllerAs: 'vm',
  24:              templateUrl: function (element) {
  25:                  return '/app/components/postal-lookup.html';
  26:              }
  27:          };
  28:          return directive;
  29:   
  30:          function link(scope, element, attrs) {
  31:          }
  32:      }
  33:   
  34:   
  35:      angular
  36:          .module('app')
  37:          .controller('stPostalLookupController', ['$scope','$http','$q','common','config', stPostalLookupController]);
  38:   
  39:      function stPostalLookupController($scope, $http, $q, common, config) {
  40:          //var vm = this;
  41:          $scope.address = undefined;
  42:          $scope.getLocation = getLocation;
  43:          $scope.provideAddress = provideAddress;
  44:   
  45:          function getLocation(val) {
  46:   
  47:              var geocoder = new google.maps.Geocoder();
  48:              var address = val;
  49:   
  50:              var deferred = $q.defer();
  51:   
  52:              if (geocoder) {
  53:                  geocoder.geocode({ 'address': address }, function (results, status) {
  54:                      if (status == google.maps.GeocoderStatus.OK) {
  55:                          var data = Enumerable.From(results)
  56:                                      .Where(function (x) {
  57:                                          return x.address_components && x.address_components.length > 4 && x.formatted_address
  58:                                      })
  59:                                      .Select(function (x) {
  60:                                          var streetNo = _extractStreetNo(x.address_components);
  61:                                          var streetAdreess = _extractStreetAddress(x.address_components);
  62:                                          if (!angular.isDefined(streetAdreess)) {
  63:                                              streetAdreess = '';
  64:                                          }
  65:                                          if (!angular.isDefined(streetNo)) {
  66:                                              streetNo = '';
  67:                                          }
  68:                                          return {
  69:                                              title: x.formatted_address,
  70:                                              city: _extractCity(x.address_components),
  71:                                              state: _extractState(x.address_components),
  72:                                              streetAddress: sprintf("%(streetNumber)s %(streetAddress)s", { streetNumber: streetNo, streetAddress: streetAdreess }),
  73:                                              country: _extractCountry(x.address_components),
  74:                                              postalCode: _extractPostalCode(x.address_components)
  75:                                          }
  76:                                      }).ToArray()
  77:                          if (data && data.length > 0)
  78:                              deferred.resolve(data);
  79:                          else
  80:                              deferred.resolve([]);
  81:                      }
  82:                      else {
  83:                          deferred.resolve([]);
  84:                      }
  85:                  });
  86:              }
  87:   
  88:              return deferred.promise;
  89:   
  90:             
  91:          };
  92:   
  93:   
  94:          function provideAddress() {
  95:              if ($scope.addressCallback && angular.isFunction($scope.addressCallback)) {
  96:                 
  97:                  $scope.addressCallback({address:$scope.address});
  98:              }
  99:              else {
 100:                  if ($scope.address && $scope.address.city && $scope.address.state) {
 101:                      common.$broadcast(config.events.ADDRESS_COPIED, { address: $scope.address });
 102:                  }
 103:              }
 104:              
 105:          }
 106:   
 107:          function _extractState(comps) {
 108:              return Enumerable.From(comps)
 109:                          .Where(function (comp) {
 110:                              return Enumerable.From(comp.types)
 111:                                          .Any(function (type) {
 112:                                              return type.toLowerCase() == "administrative_area_level_1"
 113:                                          })
 114:                          })
 115:                          .Select(function (comp) {
 116:                              return {
 117:                                  name: comp.long_name,
 118:                                  code: comp.short_name
 119:                              }
 120:                          }).FirstOrDefault()
 121:          }
 122:          function _extractCity(comps) {
 123:              return Enumerable.From(comps)
 124:                          .Where(function (comp) {
 125:                              return Enumerable.From(comp.types)
 126:                                          .Any(function (type) {
 127:                                              return type.toLowerCase() == "neighborhood" || type.toLowerCase() == "locality";
 128:                                          })
 129:                          })
 130:                          .Select(function (comp) {
 131:                              return comp.long_name;
 132:                          }).FirstOrDefault()
 133:          }
 134:   
 135:          function _extractStreetAddress(comps) {
 136:              return Enumerable.From(comps)
 137:                          .Where(function (comp) {
 138:                              return Enumerable.From(comp.types)
 139:                                          .Any(function (type) {
 140:                                              return type.toLowerCase() == "route";
 141:                                          })
 142:                          })
 143:                          .Select(function (comp) {
 144:                              return comp.long_name;
 145:                          }).FirstOrDefault()
 146:          }
 147:          function _extractStreetNo(comps) {
 148:              return Enumerable.From(comps)
 149:                          .Where(function (comp) {
 150:                              return Enumerable.From(comp.types)
 151:                                          .Any(function (type) {
 152:                                              return type.toLowerCase() == "street_number";
 153:                                          })
 154:                          })
 155:                          .Select(function (comp) {
 156:                              return comp.long_name;
 157:                          }).FirstOrDefault()
 158:          }
 159:          function _extractCountry(comps) {
 160:              return Enumerable.From(comps)
 161:                          .Where(function (comp) {
 162:                              return Enumerable.From(comp.types)
 163:                                          .Any(function (type) {
 164:                                              return type.toLowerCase() == "country";
 165:                                          })
 166:                          })
 167:                          .Select(function (comp) {
 168:                              return {
 169:                                  name: comp.long_name,
 170:                                  code: comp.short_name
 171:                              }
 172:                          }).FirstOrDefault()
 173:          }
 174:          function _extractPostalCode(comps) {
 175:              return Enumerable.From(comps)
 176:                          .Where(function (comp) {
 177:                              return Enumerable.From(comp.types)
 178:                                          .Any(function (type) {
 179:                                              return type.toLowerCase() == "postal_code";
 180:                                          })
 181:                          })
 182:                          .Select(function (comp) {
 183:                              return comp.long_name;
 184:                          }).FirstOrDefault()
 185:          }
 186:      }
 187:   
 188:    
 189:      
 190:  })();



The template looks like this:


   1:  <label class="control-label block">
   2:      Address Lookup: 
   3:  </label>
   4:  <input type="text" ng-model="address" placeholder="Start typing full address here" typeahead-min-length="5" typeahead="address as address.title for address in getLocation($viewValue)" typeahead-loading="loadingLocations" class="control-l" typeahead-wait-ms="500" />
   5:  <i ng-show="loadingLocations" class="fa fa-refresh"></i>
   6:  <address ng-if="address.city">
   7:      <p>{{address.streetAddress}}</p>
   8:      <p>{{address.city}}, {{address.state.name}} {{address.postal_code}}</p>
   9:  </address>
  10:  <span class="btn btn-primary" ng-if="address.city" ng-click="provideAddress()">Copy Address</span>



When you use it in view, the definition would look like this:

<div st-postal-lookup address-callback="vm.copyAddress(address)"></div>

Basically, when address lookup’s copy address is clicked, vm.copyAddress will be called and then you can do whatever you like it.

1 comment:

  1. The AngularJS Global API is a set of global JavaScript functions for performing common tasks like:

    Comparing objects
    Iterating objects
    Converting data
    The Global API functions are accessed using the angular object. Know more @ Google's Top Ranked site for AngularJS Training - http://www.credosystemz.com/training-in-chennai/best-angularjs-training-in-chennai/

    ReplyDelete