控制器和指令之间的双向绑定,控制器中的 $watch 不起作用

Two-way binding between controller and directive with $watch in controller not working

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

控制器

$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 函数仅在页面加载时检测到变化,但不会再检测到。向控制器提供用户在指令的输入元素中输入的值的正确方法是什么?

这是您的选择

选项#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: '&'
        }

//在 link 函数中调用父控制器方法作为

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

选项#3 通过函数引用

控制器和指令范围与选项#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 以获得更好的可读性。

尽量避免观察变量。

正如angular docs所说:

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.

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

即:

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