Search code examples
javascriptangularjsformsangularjs-ng-clickng-submit

Proper use of ng-submit


I have a form that submits a location to Google's Geocoder and returns the lat/long and changes the map. If I use ng-click on the icon it doesn't work unless I click on it twice. If I use ng-submit on the form it appends to the url and doesn't perform the task. I feel like I'm close to getting this to work but I'm lost as to what I'm doing wrong.

Below is the form

<li>
  <form action="" class="search-form" ng-submit="convertLatLonToAddress()">
      <div class="form-group has-feedback">
      <label for="search" class="sr-only">Search</label>
      <input type="text" class="form-control" name="search" id="search" placeholder="Search for an address or place name">
          <i class="fa fa-search form-control-indicator"></i>                        
    </div>
  </form>
</li> 

And here is the function

$scope.convertLatLonToAddress = function(){
  var address = $('#search').val();
  var geocoder = new google.maps.Geocoder();

  geocoder.geocode( { 'address': address}, function(results, status) {
    if (status == google.maps.GeocoderStatus.OK) {
      var latitude = results[0].geometry.location.lat();
      var longitude = results[0].geometry.location.lng();
      // console.log(latitude + ' and ' + longitude);
      $scope.center.lat = latitude;
      $scope.center.lon = longitude;
    } 
  }); 
};

Thanks to @PSL it's fixed! See below:

<li>
  <form class="search-form" ng-submit="convertLatLonToAddress(searchText)">
    <div class="form-group has-feedback">
      <label for="search" class="sr-only">Search</label>
      <input type="text" class="form-control" name="search" id="search" placeholder="Search for an address or place name" ng-model="searchText">
      <button style="visibility: hidden"></button>
        <a ng-click="convertLatLonToAddress(searchText)">
          <i class="fa fa-search form-control-indicator"></i>                        
        </a>
    </div>
  </form>
</li> 

And

$scope.convertLatLonToAddress = function(searchText){
  // var address = $('#search').val();
  var address = searchText;
  var geocoder = new google.maps.Geocoder();

  geocoder.geocode( { 'address': address}, function(results, status) {
    if (status == google.maps.GeocoderStatus.OK) {
      var latitude = results[0].geometry.location.lat();
      var longitude = results[0].geometry.location.lng();
      // console.log(latitude + ' and ' + longitude);
      $scope.center.lat = latitude;
      $scope.center.lon = longitude;
      $scope.$apply();
    } 
  }); 
};

Solution

  • You need to invoke the digest cycle manually inside the async call of geocode, since geocode does not run inside angular context.

    geocoder.geocode( { 'address': address}, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
          var latitude = results[0].geometry.location.lat();
          var longitude = results[0].geometry.location.lng();
          // console.log(latitude + ' and ' + longitude);
          $scope.center.lat = latitude;
          $scope.center.lon = longitude;
          $scope.$apply();
        } 
      }); 
    

    Everytime you click, ng-click triggers the digest cycle so previous cycle runs the non angular async call and updated scope which angular is unaware, when you click on it again it runs the digest cycle again and does the same but that time the values you set previously will be picked and that is why it takes 2 clicks. For ng-submit to execute you need a form element trigger, ex: a button or input type="submit" that causes submit behavior to happen on the form. You should also remove action from form unless you really intend to do a redirection.

    Apart from that you can use ng-model on the textbox and pass the value to your function as well instead of getting value from DOM directly.

    <input type="text" class="form-control" name="search" id="search" placeholder="Search for an address or place name" ng-model="searchText">
    

    and pass the value via ng-click as ng-click="convertLatLonToAddress(searchText)" and use it inside your function.

    In order to avoid scope.apply(); in your controller you could abstract out geoCoder to an angular service and return a promise (creating deferred object) and use that service in your controller.

    myApp.service('geoCoderService', ['$q', function($q){
          this.getCoordinates = function(address){
              var defer = $q.defer();
               var geocoder = new google.maps.Geocoder();
    
              geocoder.geocode( { 'address': address}, function(results, status) {
               if (status == google.maps.GeocoderStatus.OK) {
                 var latitude = results[0].geometry.location.lat();
                 var longitude = results[0].geometry.location.lng();
                 return defer.resolve({latitude :latitude , longitude :longitude });
               } 
              //faliure
              defer.reject(status);
           }); 
              return defer.promise;
          }
    
    });
    

    inject geoCoderService and get data using:

     geoCoderService.getCoordinates(address).then(function(coordinates){
         //populate it
     }).catch(function(errorStatus){  /*ooops Error*/ })