AngularJS form validation inside google places dir

2019-08-09 17:24发布

问题:

I have an input field that is create by the following directive:

.directive('googlePlaces', function(){
        return {
            restrict:'E',
            replace:true,
            scope: {location:'='},
            template: function (elem, attrs) { 
                return '<div><div class="form-group" ng-class="{ \'has-error\' : '+attrs.form+'.google_places_ac.$invalid }"><label>Address*</label><input id="google_places_ac" required name="google_places_ac" type="text" class="form-control" placeholder="Address" /><p class="help-block" ng-message="required" ng-show="'+attrs.form+'.google_places_ac.$invalid">Message</p></div></div>'
            },
            link: function($scope, elm, attrs){
                var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
                google.maps.event.addListener(autocomplete, 'place_changed', function() {
                    var place = autocomplete.getPlace();
                    $scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
                    $scope.$apply();
                });
            }
        }
    })

I have been trying exhaustively to add required validation to this field, but it doesn't work. I do the same for other input fields in my HTML form and it works fine.

This is the relevant HTML:

<form name="registrationForm" ng-submit="register()" novalidate>
...
<div class="col-xs-12">
            <google-places location=location form="registrationForm"></google-places>
</div>
...

I've tried many different things, scope: {location:'=', form:'='}, $compile, just adding directly the name registrationForm, or simply form. None of it worked.

I would really appreciate if someone could help me with this :)

回答1:

You could do this in many ways. Here are couple of them.

1) Isolate the validation and display of messages, accessing form etc from the googlePlaces directive and have the control of it given to the consumer as it really is a consumer's concern. They can have full control over how to display, what to display and where to display. This would avoid any more responsibilities to the directive who will just be responsible for providing the place selection. Have your directive require the ng-model and specify required attribute as well.

So a rough implementation would be something like this.

.directive('googlePlaces', function() {
  return {
    require:'ngModel',
    restrict: 'E',
    replace: true,
    scope: {
      location: '=ngModel'
    },
    template: function(elem, attrs) {
      return '<div><div class="form-group"><label>Address*</label><input id="google_places_ac" required name="google_places_ac" type="text" ng-model="locSearch" class="form-control" placeholder="Address" /></div><button type="button" ng-click="clear()">clear</button></div>'
    },
    link: function($scope, elm, attrs, ctrl) {
      var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
      google.maps.event.addListener(autocomplete, 'place_changed', function() {
        var place = autocomplete.getPlace();
        $scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
        $scope.$apply();
      });

      $scope.clear = function() {
        $scope.location = null;
      }
    }
  }
});

and

 <google-places name="location" ng-model=location required 
               ng-class="{ 'has-error' : registrationForm.location.$invalid }">
 </google-places>
 <span class="help-block" 
       ng-show="registrationForm.location.$invalid">Please specify location</span>

angular.module('app', []).directive('googlePlaces', function() {
  return {
    require:'ngModel',
    restrict: 'E',
    replace: true,
    scope: {
      location: '=ngModel'
    },
    template: function(elem, attrs) {
      return '<div><div class="form-group"><label>Address*</label><input id="google_places_ac" required name="google_places_ac" type="text" ng-model="location" class="form-control" placeholder="Address" /></div><button type="button" ng-click="clear()">clear</button></div>'
    },
    link: function($scope, elm, attrs, ctrl) {
      var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
      google.maps.event.addListener(autocomplete, 'place_changed', function() {
        var place = autocomplete.getPlace();
        $scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
        $scope.$apply();
      });

      $scope.clear = function() {
        $scope.location = null;
      }
    }
  }
});
.has-error input {
  border: 2px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?libraries=places"></script>
<div ng-app="app">
  <form name="registrationForm" ng-submit="register()" novalidate>
    <div class="col-xs-12">
    
      <google-places name="location" ng-model=location required ng-class="{ 'has-error' : registrationForm.location.$invalid }"></google-places>
      <span class="help-block" ng-show="registrationForm.location.$invalid">Please specify location</span>
    </div>
  </form>
</div>

2) You can 2 way bind the form object to the directive and control the validation and display message from there. You would need to place an ng-model on the input in order for validation to kick in properly.

.directive('googlePlaces', function() {
  return {
    restrict: 'E',
    replace: true,
    scope: {
      location: '=',
      form:'='
    },
    template: function(elem, attrs) {
      return '<div><div class="form-group" ng-class="{ \'has-error\' :form.google_places_ac.$invalid }"><label>Address*</label><input ng-model="selectedLocation" id="google_places_ac" required name="google_places_ac" type="text" class="form-control" placeholder="Address" /><p class="help-block" ng-message="required" ng-show="form.google_places_ac.$invalid">Message</p></div></div>'
    },
    link: function($scope, elm, attrs) {

      var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
      google.maps.event.addListener(autocomplete, 'place_changed', function() {

        var place = autocomplete.getPlace();
        $scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
        $scope.$apply();
      });
    }
  }
});

angular.module('app', []).directive('googlePlaces', function() {
  return {
    restrict: 'E',
    replace: true,
    scope: {
      location: '=',
      form: '='
    },
    template: function(elem, attrs) {
      return '<div><div class="form-group" ng-class="{ \'has-error\' :form.google_places_ac.$invalid }"><label>Address*</label><input ng-model="location" id="google_places_ac" required name="google_places_ac" type="text" class="form-control" placeholder="Address" /><p class="help-block" ng-message="required" ng-show="form.google_places_ac.$invalid">Message</p></div></div>'
    },
    link: function($scope, elm, attrs) {

      var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
      google.maps.event.addListener(autocomplete, 'place_changed', function() {

        var place = autocomplete.getPlace();
        $scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
        $scope.$apply();
      });
    }
  }
})
.has-error input {
  border: 2px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?libraries=places"></script>
<div ng-app="app">
  <form name="registrationForm" ng-submit="register()" novalidate>
    <div class="col-xs-12">
      <google-places location=location form="registrationForm"></google-places>
    </div>
  </form>
</div>