控制器和指令之间的双向绑定,控制器中的 $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: ''};
...
我遇到过不少类似的问题,都试过了都没有成功。我不确定我是否在 "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: ''};
...