考虑这个Plunker这具有我所追求的行为。
问题在于 1)我认为我做错了,2)它使用了深表,这很昂贵并且应该是不必要的。
这个例子有点做作。我的应用程序中实际上拥有的是一个功能齐全的预输入/自动完成控件,它绑定(bind)到对象并根据表达式呈现它们。 plunker 被精简以仅显示与该问题相关的部分; 如何正确实现这种事情?
主要困难是确保在输入中输入“某些内容”并单击按钮应该传播到指令中并导致 span.outlet
进行相应更新。即,单击 Alpha 并尝试更改描述应该会导致页面上显示“Selected: A - Something”。
如果我删除深度监视,除非我用新的对象引用替换 $scope.selected 而不是仅仅更改现有对象引用上的属性(请参阅 punker 的 mainCtrl 中的注释),否则不会发生这种情况。
因此,第一个要求是自定义指令处理对象,而不仅仅是字符串。
第二个要求是该指令必须能够更新 span.outlet
每当 $scope.selected
对象从自定义指令外部更改。
第三,指令应该尽可能高效。这就是我提出这个问题的原因。 ng-model allready 内部有浅 $watch,现在我在其上添加了一个深 $watch,这对性能不利。有没有办法在没有如此深入的 $watch 的情况下做到这一点?
最后,如果我不必拥有scope.ngModel 绑定(bind),那就太好了。感觉很脏。
相关标记:
<input ng-model="newDescription">
<button ng-click="setNewDescription(newDescription)">Set description</button>
<hr/>
<my-list ng-model="selected" expression="{{::expression}}" options="options"></my-list>
相关主控代码:
$scope.options = [
{key:'A', desc: 'Alpha'},
{key:'B', desc: 'Beta'},
{key:'G', desc: 'Gamma'},
{key:'D', desc: 'Delta'}
];
$scope.selected = $scope.options[1];
$scope.expression = '{{key}} - {{desc}}';
myList 指令:
app.directive('myList', ['$interpolate', function($interpolate) {
return {
restrict: 'E',
require: 'ngModel',
replace: true,
template: '<div>Selected: <span class="outlet"></span><ul><li ng-repeat="item in vm.items"><a href="" ng-click="vm.select(item)">{{vm.render(item)}}</a></li></ul></div>',
scope: {
ngModel: '=',
items: '=options'
},
link: Link,
controller: Ctrl,
controllerAs: 'vm',
bindToController: true
};
function Link(scope, element, attrs, ngModelCtrl) {
var outlet = element.children()[0];
scope.vm.render = $interpolate(attrs.expression);
ngModelCtrl.$formatters.push(function(modelValue) {
if (ngModelCtrl.$isEmpty(modelValue))
return '';
else
return scope.vm.render(modelValue);
});
ngModelCtrl.$render = function() {
console.log('rendering', ngModelCtrl.$viewValue);
outlet.textContent = scope.vm.render(ngModelCtrl.$modelValue);
};
ngModelCtrl.$parsers.push(function(value) {
// Only gets called due to the $setViewValue call in the deep $watch.
// We don't have a way of going from a string to an object, but the $modelValue contains the right thing.
console.log('parsing', value);
return ngModelCtrl.$modelValue;
});
// I would prefer it if I could solve this without a deep watch on ngModel!
scope.$watch('vm.ngModel', function(n, o) {
if (angular.equals(n, o)) return;
console.log('ngModel $watch\r\n', o, '->\r\n', n);
ngModelCtrl.$setViewValue(scope.vm.render(n));
ngModelCtrl.$render();
}, true);
}
function Ctrl($scope) {
this.select = function(item) {
console.log('selecting', item);
this.ngModel = item;
}.bind(this);
}
}]);
非常感谢任何有关此问题的帮助,因为我已经尝试解决这个问题有一段时间了。谢谢!
最佳答案
我不确定我是否完全理解您的需求,但我准备了sample
app.directive('myList', ['$interpolate', function($interpolate) {
return {
restrict: 'E',
replace: true,
template: '<div>Selected: <span class="outlet"></span><ul><li ng-repeat="item in vm.items"><a href="" ng-click="vm.select(item)">{{vm.render(item)}}</a></li></ul></div>',
scope: {
selected: '=',
items: '=options'
},
link: Link,
controller: Ctrl,
controllerAs: 'vm',
bindToController: true
};
function Link(scope, element, attrs, ctrl) {
var outlet = element.children()[0];
scope.vm.render = $interpolate(attrs.expression);
scope.$watch(function () {
return ctrl.selected;
}, function (value) {
console.log('rendering', value);
outlet.textContent = scope.vm.render(value);
});
}
function Ctrl($scope) {
this.select = function(item) {
console.log('selecting', item);
this.selected = item;
}.bind(this);
}
}]);
HTML:
<input ng-model="newDescription">
<button ng-click="setNewDescription(newDescription)">Set description</button>
<hr/>
<my-list selected="selected" expression="{{::expression}}" options="options"></my-list>
更新2
Sample使用ng-model
。您不需要在 $watch 中调用 $render 函数。模型自动更改后调用。
也是如何使用 ngModel here 创建指令的好例子.
关于javascript - 如何检测自定义指令内 ng-model 对象的更改?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30617062/