1

I've come across a quite a few similar questions and tried them all with no success. I'm not sure if I'm doing this the "Angular way" so I might need to change my approach. In short I'm initialising a scoped variable from within a controller, the variable is then shared with the directive's scope. However when this value is changed as part of a user interaction the new value is not being synchronised with the controllers variable. My abbreviated code follows:

Controller

The $watch method is only called once i.e. when the page loads.

.controller('NewTripCtrl', ['$scope', function($scope) {
  $scope.pickupLocation;
  $scope.dropoffLocation;

  $scope.$watch('dropoffLocation', function(oldVal, newVal) {
    console.log('dropofflocation has changed.');  
    console.log(oldVal);
    console.log(newVal);
  });
}])

HTML

<div class="padding">
  <input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Pickup Address" location="pickupLocation"></input>
</div>

<div class="padding">
  <input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Dropoff Address" location="dropoffLocation"></input>
</div> 

Directive

angular.module('ion-google-place', [])
  .directive('ionGooglePlace', [
    '$ionicTemplateLoader',
    '$ionicBackdrop',
    '$q',
    '$timeout',
    '$rootScope',
    '$document',
    function ($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $document) {
      return {
        restrict: 'A',
        scope: {
          location: '=location'
        },
        link: function (scope, element, attrs) {
          scope.locations = [];
          console.log('init');
          console.log(scope);
          console.log(attrs);
          // var geocoder = new google.maps.Geocoder();
          var placesService = new google.maps.places.AutocompleteService();
          var searchEventTimeout = undefined;

          var POPUP_TPL = [
        '<div class="ion-google-place-container">',
        '<div class="bar bar-header item-input-inset">',
        '<label class="item-input-wrapper">',
        '<i class="icon ion-ios7-search placeholder-icon"></i>',
        '<input class="google-place-search" type="search" ng-model="searchQuery" placeholder="' + 'Enter an address, place or ZIP code' + '">',
        '</label>',
        '<button class="button button-clear">',
        'Cancel',
        '</button>',
        '</div>',
        '<ion-content class="has-header has-header">',
        '<ion-list>',
        '<ion-item ng-repeat="l in locations" type="item-text-wrap" ng-click="selectLocation(l)">',
        '{{l.formatted_address || l.description }}',
        '</ion-item>',
        '</ion-list>',
        '</ion-content>',
        '</div>'
          ].join('');

          var popupPromise = $ionicTemplateLoader.compile({
            template: POPUP_TPL,
            scope: scope,
            appendTo: $document[0].body
          });

          popupPromise.then(function (el) {
            var searchInputElement = angular.element(el.element.find('input'));

            // Once the user has selected a Place Service prediction, go back out
            // to the Places Service and get details for the selected location.
            // Or if using Geocode Service we'll just passing through
            scope.getDetails = function (selection) {
              //console.log('getDetails');
              var deferred = $q.defer();
              if (attrs.service !== 'places') {
                deferred.resolve(selection);
              } else {
                var placesService = new google.maps.places.PlacesService(element[0]);
                placesService.getDetails({
                    'placeId': selection.place_id
                  },
                  function(placeDetails, placesServiceStatus) {
                    if (placesServiceStatus == "OK") {
                      deferred.resolve(placeDetails);
                    } else {
                      deferred.reject(placesServiceStatus);
                    }
                  });
              }
              return deferred.promise;
            };


            // User selects a Place 'prediction' or Geocode result
            // Do stuff with the selection
            scope.selectLocation = function (selection) {
              // If using Places Service, we need to go back out to the Service to get
              // the details of the place.
              var promise = scope.getDetails(selection);
              promise.then(onResolve, onReject, onUpdate);
              el.element.css('display', 'none');
              $ionicBackdrop.release();
            };

            function onResolve (details) {
              console.log('ion-google-place.onResolve');

              scope.location = details;

              $timeout(function() {
                // anything you want can go here and will safely be run on the next digest.
                scope.$apply();
              })

              if (!scope.location) {
                element.val('');
              } else {
                element.val(scope.location.formatted_address || '');
              }              
            }
            // more code ...
      };
    }
  ]);

The $watch function only detects a change when the page loads but never again. What is the correct way to provide the controller with the value that the user enters into the directive's input element?

3
  • you have scope.location, in tpl is another location in ng-repeat Commented Jan 23, 2015 at 13:55
  • Good shout but I've updated it to have a different name and $watch still isn't firing. Commented Jan 23, 2015 at 14:03
  • Can you create a fiddle for this or try using anonymous function. Something like $scope.$watch(function(){return $scope.dropoffLocation}, function(){}) ..... Maybe it helps. Commented Jan 23, 2015 at 14:56

2 Answers 2

1

As angular docs says:

Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope.

This is a common issue when doing 2-way data binding with primitives. Try doing this same but try to share an object instead of a primitive.

i.e:

.controller('NewTripCtrl', ['$scope', function($scope) {
    $scope.pickupLocation = {location: ''};
    $scope.dropoffLocation = {location: ''};
    ...
Sign up to request clarification or add additional context in comments.

2 Comments

Legend. I've gotten it working but I have to $watch "pickupLocation.location". Is there anyway to have changes to object fields/attibutes boil up to the object level?
sorry but I don't understand the question haha you can $watch pickupLocation without specifying location AFAIK BTW
1

Here are your options

Option#1 using ng-model with watch function in parent controller

<input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Dropoff Address" ng-model="dropoffLocation"></input>

and in directive

scope: {
          location: '=ngModel'
        },

You don't need Watch here.

Option#2 Through Object literal

.controller('NewTripCtrl', ['$scope', function($scope) {

 $scope.locationChanged = function(location){
    //you watch code goes here
 }

}



 <input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Dropoff Address" location="dropoffLocation" location-changed="locationChanged(location)" ></input>

//directive scope

scope: {
          location: '=',
      locationChanged: '&'
        }

//in link function invoke parent controllers method as

scope.locationChanged({location:scope.location});  

Option#3 Through Function reference

controllect and directives scope same as option#2

<input ion-google-place type="text" class="ion-google-place" autocomplete="off" service="places" placeholder="Dropoff Address" location="dropoffLocation" location-changed="locationChanged" ></input>


//in link function invoke parent controllers method as 

scope.locationChanged()(scope.location);  

Option#2 is recommended for better readability.

watching variables should be avoided as much as possible.

1 Comment

Thanks for these. Please correct me if I'm wrong but with option 1 you would still need to watch the scoped variable if you wanted to trigger some functionality when it changed, right?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.