在 Angular 中,范围变量 *before* ngModel 绑定输入的“值”的绑定属性
In Angular, bind attribute from scope variable *before* ngModel binds the input’s `value`
在我的 Angular 应用程序中,我定义了一个包含 <input type="range">
的自定义滑块指令。我的自定义指令支持 ngModel
将滑块的值绑定到一个变量。自定义指令还需要一个 fraction-size
属性。它对值进行计算,然后使用结果来设置包装的 <input>
.
的 step
值
我在组合这两个功能时看到一个错误 – ngModel
和我的绑定属性值。它们 运行 顺序错误。
这里有一个演示:
angular.module('HelloApp', []);
angular.module('HelloApp').directive('customSlider', function() {
var tpl = "2 <input type='range' min='2' max='3' step='{{stepSize}}' ng-model='theNum' /> 3";
return {
restrict: 'E',
template: tpl,
require: 'ngModel',
scope: {
fractionSize: '='
},
link: function(scope, element, attrs, ngModelCtrl) {
scope.stepSize = 1 / scope.fractionSize;
scope.$watch('theNum', function(newValue, oldValue) {
ngModelCtrl.$setViewValue(newValue);
});
ngModelCtrl.$render = function() {
scope.theNum = ngModelCtrl.$viewValue;
};
}
};
});
angular.module('HelloApp').controller('HelloController', function($scope) {
$scope.someNumber = 2.5;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<div ng-app="HelloApp" ng-controller="HelloController">
<h3>Custom slider</h3>
<custom-slider ng-model="someNumber" fraction-size="10"></custom-slider>
<h3>View/edit the slider’s value</h3>
<input ng-model="someNumber"></input>
</div>
上面的滑块应该从中间开始,代表 2.5。但它实际上一直从右边开始(代表 3)。如果您拖动滑块,或者如果您通过编辑文本字段更改其绑定值,滑块会自行修复并允许值为 2.5。
我已经弄清楚为什么会在演示中发生这种情况——我只是不知道如何解决它。目前,当一个新的自定义滑块被动态添加到页面时,包装的 <input>
的 step
是未定义的,并且默认为 1。然后 ngModel
将值设置为 2.5 – 但自从step
为 1,input
中的值四舍五入为 3。最后,step
设置为 0.1
– 太晚了。
如何确保 step
属性的值在 ngModel
设置输入的 value
之前绑定 ?
在上面的示例中,滑块在页面加载时位于页面上。在我的真实代码中,动态添加了多个新滑块。每当添加它们时,它们都应该以正确的顺序绑定它们的 step
和 value
。
我不喜欢的解决方法
解决方法是在模板中对 step
进行硬编码,而不是动态设置它。如果我这样做,我的自定义滑块指令将没有用,我会删除它并直接使用 <input>
:
<input type="range" min="2" max="3" step="0.1" ng-model="someNumber">
如果我使用它,则滑块的 value
设置正确,没有四舍五入。但我想保留我的自定义指令 customSlider
,以便抽象 step
的计算。
我尝试过但没有用的东西
- 更改模板中
step
和 ng-model
属性的顺序没有任何效果。
- 我开始尝试添加一个
compile
函数,而不仅仅是一个 link
函数。但是我无法在compile
中设置stepSize
,因为scope
在编译阶段不可用。
- 我尝试将指令的
link
函数拆分为前 link 和 post-link 函数。但是无论我在 pre
还是 post
中设置 scope.stepSize
,页面都像以前一样工作。
- 在设置
scope.stepSize
后立即手动调用 scope.$digest()
只会抛出 Error: [$rootScope:inprog] $digest already in progress
.
- 我认为自定义指令优先级不适合这个,因为只涉及一个自定义指令。我对
step
值的绑定不是自定义指令,它只是原始 {{}}
绑定。而且我认为绑定 step
是一项足够简单的任务,不应将其包装在自己的指令中。
我不确定这是否是正确的方法,但是向主控制器添加超时可以解决问题。
$timeout(function() {
$scope.someNumber = 2.5;
});
编辑:虽然一开始看起来像是一个肮脏的 hack,但请注意,(最终)$scope 变量的分配晚于模板是很常见的,因为需要额外的 ajax 调用来检索值.
在您的 link
函数中,通过添加 step
属性来操作 DOM。
您还可以通过将 theNum: '=ngModel'
放入 scope
来简化与外部 ngModel
的绑定。
var app = angular.module('HelloApp', []);
app.directive('customSlider', function () {
var tpl = "2 <input type='range' min='2' max='3' ng-model='theNum' /> 3";
return {
restrict: 'E',
template: tpl,
require: 'ngModel',
scope: {
fractionSize: '=',
theNum: '=ngModel'
},
link: function (scope, element, attrs, ngModelCtrl) {
var e = element.find('input')[0];
var step = 1 / scope.fractionSize;
e.setAttribute('step', step);
scope.$watch('fractionSize', function (newVal) {
if (newVal) {
var step = 1 / newVal;
e.setAttribute('step', step);
}
});
}
};
});
angular.module('HelloApp').controller('HelloController', function($scope) {
$scope.someNumber = 2.5;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<div ng-app="HelloApp" ng-controller="HelloController">
<h3>Custom slider</h3>
<custom-slider ng-model="someNumber" fraction-size="10"></custom-slider>
<h3>View/edit the slider’s value</h3>
<input ng-model="someNumber"></input> {{ someNumber }}
</div>
进一步 直接 DOM 操作,我根本不会在内部输入上有另一个 ng-model
,而是总是 set/get 上的属性直接输入
当 setting/getting 值来自输入时,您需要考虑一个抽象级别。原始 DOM 事件和元素。
因此,您不受 Angular 允许的此类元素的限制(事实上:如果没有解决方法,它所做的似乎无法处理您的用例)。如果浏览器允许范围输入,你可以这样做。
有 2 个 ngModelControllers 在一个 UI 设置一个值的小部件上运行可能会有点混乱,至少对我来说是这样!
如果您 need/want 使用它,您仍然可以访问外部 ngModelController 管道及其所有关于解析器和验证器的功能。
您可以节省额外的观察者(但这可能是 micro/premature 优化)。
请参阅下面的示例。
angular.module('HelloApp', []);
angular.module('HelloApp').directive('customSlider', function() {
var tpl = "2 <input type='range' min='2' max='3' /> 3";
return {
restrict: 'E',
template: tpl,
require: 'ngModel',
scope: {
fractionSize: '='
},
link: function(scope, element, attrs, ngModelCtrl) {
var input = element.find('input');
input.prop('step', 1 / scope.fractionSize);
input.on('input', function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue(input.prop('value'));
});
});
ngModelCtrl.$render = function(value) {
input.prop('value', ngModelCtrl.$viewValue);
};
}
};
});
angular.module('HelloApp').controller('HelloController', function($scope) {
$scope.someNumber = 2.5;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<div ng-app="HelloApp" ng-controller="HelloController">
<h3>Custom slider</h3>
<custom-slider ng-model="someNumber" fraction-size="10"></custom-slider>
<h3>View/edit the slider’s value</h3>
<input ng-model="someNumber"></input>
</div>
在我的 Angular 应用程序中,我定义了一个包含 <input type="range">
的自定义滑块指令。我的自定义指令支持 ngModel
将滑块的值绑定到一个变量。自定义指令还需要一个 fraction-size
属性。它对值进行计算,然后使用结果来设置包装的 <input>
.
step
值
我在组合这两个功能时看到一个错误 – ngModel
和我的绑定属性值。它们 运行 顺序错误。
这里有一个演示:
angular.module('HelloApp', []);
angular.module('HelloApp').directive('customSlider', function() {
var tpl = "2 <input type='range' min='2' max='3' step='{{stepSize}}' ng-model='theNum' /> 3";
return {
restrict: 'E',
template: tpl,
require: 'ngModel',
scope: {
fractionSize: '='
},
link: function(scope, element, attrs, ngModelCtrl) {
scope.stepSize = 1 / scope.fractionSize;
scope.$watch('theNum', function(newValue, oldValue) {
ngModelCtrl.$setViewValue(newValue);
});
ngModelCtrl.$render = function() {
scope.theNum = ngModelCtrl.$viewValue;
};
}
};
});
angular.module('HelloApp').controller('HelloController', function($scope) {
$scope.someNumber = 2.5;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<div ng-app="HelloApp" ng-controller="HelloController">
<h3>Custom slider</h3>
<custom-slider ng-model="someNumber" fraction-size="10"></custom-slider>
<h3>View/edit the slider’s value</h3>
<input ng-model="someNumber"></input>
</div>
上面的滑块应该从中间开始,代表 2.5。但它实际上一直从右边开始(代表 3)。如果您拖动滑块,或者如果您通过编辑文本字段更改其绑定值,滑块会自行修复并允许值为 2.5。
我已经弄清楚为什么会在演示中发生这种情况——我只是不知道如何解决它。目前,当一个新的自定义滑块被动态添加到页面时,包装的 <input>
的 step
是未定义的,并且默认为 1。然后 ngModel
将值设置为 2.5 – 但自从step
为 1,input
中的值四舍五入为 3。最后,step
设置为 0.1
– 太晚了。
如何确保 step
属性的值在 ngModel
设置输入的 value
之前绑定 ?
在上面的示例中,滑块在页面加载时位于页面上。在我的真实代码中,动态添加了多个新滑块。每当添加它们时,它们都应该以正确的顺序绑定它们的 step
和 value
。
我不喜欢的解决方法
解决方法是在模板中对 step
进行硬编码,而不是动态设置它。如果我这样做,我的自定义滑块指令将没有用,我会删除它并直接使用 <input>
:
<input type="range" min="2" max="3" step="0.1" ng-model="someNumber">
如果我使用它,则滑块的 value
设置正确,没有四舍五入。但我想保留我的自定义指令 customSlider
,以便抽象 step
的计算。
我尝试过但没有用的东西
- 更改模板中
step
和ng-model
属性的顺序没有任何效果。 - 我开始尝试添加一个
compile
函数,而不仅仅是一个link
函数。但是我无法在compile
中设置stepSize
,因为scope
在编译阶段不可用。 - 我尝试将指令的
link
函数拆分为前 link 和 post-link 函数。但是无论我在pre
还是post
中设置scope.stepSize
,页面都像以前一样工作。 - 在设置
scope.stepSize
后立即手动调用scope.$digest()
只会抛出Error: [$rootScope:inprog] $digest already in progress
. - 我认为自定义指令优先级不适合这个,因为只涉及一个自定义指令。我对
step
值的绑定不是自定义指令,它只是原始{{}}
绑定。而且我认为绑定step
是一项足够简单的任务,不应将其包装在自己的指令中。
我不确定这是否是正确的方法,但是向主控制器添加超时可以解决问题。
$timeout(function() {
$scope.someNumber = 2.5;
});
编辑:虽然一开始看起来像是一个肮脏的 hack,但请注意,(最终)$scope 变量的分配晚于模板是很常见的,因为需要额外的 ajax 调用来检索值.
在您的 link
函数中,通过添加 step
属性来操作 DOM。
您还可以通过将 theNum: '=ngModel'
放入 scope
来简化与外部 ngModel
的绑定。
var app = angular.module('HelloApp', []);
app.directive('customSlider', function () {
var tpl = "2 <input type='range' min='2' max='3' ng-model='theNum' /> 3";
return {
restrict: 'E',
template: tpl,
require: 'ngModel',
scope: {
fractionSize: '=',
theNum: '=ngModel'
},
link: function (scope, element, attrs, ngModelCtrl) {
var e = element.find('input')[0];
var step = 1 / scope.fractionSize;
e.setAttribute('step', step);
scope.$watch('fractionSize', function (newVal) {
if (newVal) {
var step = 1 / newVal;
e.setAttribute('step', step);
}
});
}
};
});
angular.module('HelloApp').controller('HelloController', function($scope) {
$scope.someNumber = 2.5;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<div ng-app="HelloApp" ng-controller="HelloController">
<h3>Custom slider</h3>
<custom-slider ng-model="someNumber" fraction-size="10"></custom-slider>
<h3>View/edit the slider’s value</h3>
<input ng-model="someNumber"></input> {{ someNumber }}
</div>
进一步 ng-model
,而是总是 set/get 上的属性直接输入
当 setting/getting 值来自输入时,您需要考虑一个抽象级别。原始 DOM 事件和元素。
因此,您不受 Angular 允许的此类元素的限制(事实上:如果没有解决方法,它所做的似乎无法处理您的用例)。如果浏览器允许范围输入,你可以这样做。
有 2 个 ngModelControllers 在一个 UI 设置一个值的小部件上运行可能会有点混乱,至少对我来说是这样!
如果您 need/want 使用它,您仍然可以访问外部 ngModelController 管道及其所有关于解析器和验证器的功能。
您可以节省额外的观察者(但这可能是 micro/premature 优化)。
请参阅下面的示例。
angular.module('HelloApp', []);
angular.module('HelloApp').directive('customSlider', function() {
var tpl = "2 <input type='range' min='2' max='3' /> 3";
return {
restrict: 'E',
template: tpl,
require: 'ngModel',
scope: {
fractionSize: '='
},
link: function(scope, element, attrs, ngModelCtrl) {
var input = element.find('input');
input.prop('step', 1 / scope.fractionSize);
input.on('input', function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue(input.prop('value'));
});
});
ngModelCtrl.$render = function(value) {
input.prop('value', ngModelCtrl.$viewValue);
};
}
};
});
angular.module('HelloApp').controller('HelloController', function($scope) {
$scope.someNumber = 2.5;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<div ng-app="HelloApp" ng-controller="HelloController">
<h3>Custom slider</h3>
<custom-slider ng-model="someNumber" fraction-size="10"></custom-slider>
<h3>View/edit the slider’s value</h3>
<input ng-model="someNumber"></input>
</div>