控制器和指令之间的双向绑定,控制器中的$ watch无效

时间:2015-01-23 13:20:15

标签: angularjs angularjs-directive angularjs-scope

我遇到了很多类似的问题并尝试了所有这些问题都没有成功。我不确定我是否正在以“Angular方式”这样做,所以我可能需要改变我的方法。简而言之,我正在从控制器中初始化一个范围变量,然后该变量与指令的范围共享。但是,当此值作为用户交互的一部分更改时,新值不会与controllers变量同步。我的缩写代码如下:

控制器

$ watch方法只调用一次,即页面加载时。

.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> 

指令

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 ...
      };
    }
  ]);

$ watch功能仅在页面加载时检测到更改,但从不再检测到。向控制器提供用户输入指令输​​入元素的值的正确方法是什么?

2 个答案:

答案 0 :(得分:1)

以下是您的选择

选项#1使用ng-model 在父控制器中使用监视功能

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

并在指令

scope: {
          location: '=ngModel'
        },

你在这里不需要Watch

选项#2通过对象字面

.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>

//指令范围

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

//在链接函数中调用父控制器方法为

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

选项#3通过功能参考

controllect和指令范围与选项#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);  

建议选项#2以提高可读性。

应尽可能避免观察变量。

答案 1 :(得分:1)

正如angular docs所说:

  

范围继承通常很简单,你甚至不需要知道它正在发生......直到你尝试双向数据绑定(即表单元素,ng-模型)到一个原语(例如,数字,字符串,布尔值)在子范围内从父范围定义。

使用基元进行双向数据绑定时,这是一个常见问题。尝试这样做,但尝试共享一个对象而不是原始。

即:

.controller('NewTripCtrl', ['$scope', function($scope) {
    $scope.pickupLocation = {location: ''};
    $scope.dropoffLocation = {location: ''};
    ...