如何使用 Angular JS 将一个模型绑定到多个输入
How to bind one model to multiple inputs with Angular JS
我有一个用于 MySQL 日期字段的表单输入。例如:2015-01-31
.
我想让用户使用 3 种不同的表单输入法来输入。一年一个,一个月一个,一天一个。
显然 ng-model 不会开箱即用,因为我试图将日期字符串的 部分 绑定到每个输入。我很确定这样做的方法是再见创建三个 "temporary" 范围 vars/models
$scope.year;
$scope.month;
$scope.day;
...然后以某种方式 combine/binding 将它们变为实际值。
//If only it were this easy!
$scope.date = $scope.year + "-" + $scope.month + "-" + $scope.day;
上面的行当然不起作用,因为值不是双向绑定的。如果表单仅用于保存新数据,我可以通过在提交时组合输入来摆脱它。但我也需要 handle/show 现有数据。如果我想不出一种方法来控制 Angular 的绑定魔法来做我想做的事情,它会变得非常丑陋。
我找到了 this question which I think is trying to do the same thing, but they solve it with a custom directive, which is something I'm hoping to avoid. I know this may be a more maintainable/portable/modular way to do it, but I'm new to Angular and a bit intimidated by that. Also, the inputs are using the lovely angular-selectize 指令,它为该方法增加了一层额外的复杂性。
这是一个有趣的演示,它使用的自定义指令比您链接到的指令要简单得多。您应该能够将它们应用到您的输入中,而不会与其他内容发生太多冲突:
http://plnkr.co/edit/3a8xnCfJFJrBlDRTUBG7?p=preview
诀窍是使用指令为模型设置解析器和格式化程序。这使您可以拦截对模型的更改并与范围的其余部分进行交互:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.date = new Date();
});
app.directive('datePartInput', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
var part = attrs.part;
var modelToUser, userToModel
console.log('part:', part);
if (part == 'year') {
modelToUser = function(date) {
return date.getFullYear();
}
userToModel = function(year) {
ngModel.$modelValue.setYear(year);
return ngModel.$modelValue
}
}
else if (part == 'month') {
modelToUser = function(date) {
return date.getMonth();
}
userToModel = function(month) {
ngModel.$modelValue.setMonth(month);
return ngModel.$modelValue;
}
}
else if (part == 'day') {
modelToUser = function(date) {
return date.getUTCDate();
};
userToModel = function(day) {
ngModel.$modelValue.setUTCDate(day);
return ngModel.$modelValue;
};
}
ngModel.$formatters.push(modelToUser);
ngModel.$parsers.push(userToModel);
}
}
})
和模板:
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
{{date | date}}
<input date-part-input part="year" ng-model="date">
<input date-part-input part="month" ng-model="date">
<input date-part-input part="day" ng-model="date">
</body>
您可以创建一个可重复使用的指令,其中包含三个字段,以便它可以用于所有日期字段。该指令的模型在隔离范围内别名为 date
。为了获取每个日期部分,然后拆分日期,然后将 year
、month
和 day
分配给范围属性。然后,当更改其中一个字段时,日期 属性 通过将它们与 -
分隔符一起附加来更新。
对于这个指令,我只是硬编码,年月日。我建议使用一些 javascript 日期函数来填充它们,这样它们就不会被硬编码。
angular
.module('app')
.directive('dateSelect', function (){
return {
restrict: 'E',
replace: true,
scope: {
date:'=ngModel'
},
template: '<div class="dateSelect"><div class="dateField"><selectize placeholder="Select a year..." config="yearConfig" ng-model="year" ng-change="dateChanged()"></selectize></div>' +
'<div class="dateField"><selectize placeholder="Select a month..." config="monthConfig" ng-model="month" ng-change="dateChanged()"></selectize></div>' +
'<div class="dateField"><selectize placeholder="Select a day..." config="dayConfig" ng-model="day" ng-change="dateChanged()"></selectize></div></div>',
controller: function ($scope) {
$scope.yearConfig = {
options: [{value: 2013, text: '2013'}, {value: 2014, text:'2014'}, {value: 2015, text:'2015'}],
create: true,
sortField: 'value',
maxItems: 1,
};
$scope.monthConfig = {
options: [{value: '01', text: '1'}, {value: '02', text: '2'}, {value: '03', text:'3'},
{value: '04', text: '4'}, {value: '05', text:'5'}, {value: '06', text:'6'}, {value: '07', text: '7'}, {value: '08', text:'8'}, {value: '09', text:'9'},
{value: '10', text: '10'}, {value: '11', text:'11'}, {value: '12', text:'12'}],
create: true,
sortField: 'value',
maxItems: 1,
};
$scope.dayConfig = {
options: [{value: '01', text: '1'}, {value: '02', text: '2'}, {value: '03', text:'3'},
{value: '04', text: '4'}, {value: '05', text:'5'}, {value: '06', text:'6'}, {value: '07', text: '7'}, {value: '08', text:'8'}, {value: '09', text:'9'},
{value: '10', text: '10'}, {value: '11', text:'11'}, {value: '12', text:'12'}],
create: true,
sortField: 'value',
maxItems: 1,
};
$scope.dateChanged = function () {
if (!angular.isUndefined($scope.year) && !angular.isUndefined($scope.month) && !angular.isUndefined($scope.day)) {
$scope.date = $scope.year + "-" + $scope.month + "-" + $scope.day;
}
}
if (!angular.isUndefined($scope.date)) {
var dateParts = $scope.date.split("-");
if (dateParts.length === 3) {
$scope.year = dateParts[0];
$scope.month = dateParts[1];
$scope.day = dateParts[2];
}
}
}
};
});
指令可能是最好的,但这些示例看起来过于复杂。无论如何,如果您希望避免指令,只需使用 $scope.$watch
并在每次更新其中一个重要变量时重新构建日期字符串。
您的控制器中可能有这样的东西:
$scope.year = '';
$scope.month = '';
$scope.day = '';
// this might be able to be refactored
$scope.$watch('year', buildDate);
$scope.$watch('month', buildDate);
$scope.$watch('day', buildDate);
function buildDate() {
$scope.date = $scope.year + "-" + $scope.month + "-" + $scope.day;
}
附带说明一下,这可能也是我的指令逻辑的样子。
编辑:代码清理和 fiddle
更清晰的示例 - 我更喜欢这个,因为它将所有与日期相关的项目分组到一个对象中,这也使得观察变化更容易。
$scope.date = {
year: '',
month: '',
day: ''
};
// use watch collection to watch properties on the main 'date' object
$scope.$watchCollection('date', buildDate);
function buildDate(date) {
$scope.dateString = date.year + "-" + date.month + "-" + date.day;
}
我有一个用于 MySQL 日期字段的表单输入。例如:2015-01-31
.
我想让用户使用 3 种不同的表单输入法来输入。一年一个,一个月一个,一天一个。
显然 ng-model 不会开箱即用,因为我试图将日期字符串的 部分 绑定到每个输入。我很确定这样做的方法是再见创建三个 "temporary" 范围 vars/models
$scope.year;
$scope.month;
$scope.day;
...然后以某种方式 combine/binding 将它们变为实际值。
//If only it were this easy!
$scope.date = $scope.year + "-" + $scope.month + "-" + $scope.day;
上面的行当然不起作用,因为值不是双向绑定的。如果表单仅用于保存新数据,我可以通过在提交时组合输入来摆脱它。但我也需要 handle/show 现有数据。如果我想不出一种方法来控制 Angular 的绑定魔法来做我想做的事情,它会变得非常丑陋。
我找到了 this question which I think is trying to do the same thing, but they solve it with a custom directive, which is something I'm hoping to avoid. I know this may be a more maintainable/portable/modular way to do it, but I'm new to Angular and a bit intimidated by that. Also, the inputs are using the lovely angular-selectize 指令,它为该方法增加了一层额外的复杂性。
这是一个有趣的演示,它使用的自定义指令比您链接到的指令要简单得多。您应该能够将它们应用到您的输入中,而不会与其他内容发生太多冲突:
http://plnkr.co/edit/3a8xnCfJFJrBlDRTUBG7?p=preview
诀窍是使用指令为模型设置解析器和格式化程序。这使您可以拦截对模型的更改并与范围的其余部分进行交互:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.date = new Date();
});
app.directive('datePartInput', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
var part = attrs.part;
var modelToUser, userToModel
console.log('part:', part);
if (part == 'year') {
modelToUser = function(date) {
return date.getFullYear();
}
userToModel = function(year) {
ngModel.$modelValue.setYear(year);
return ngModel.$modelValue
}
}
else if (part == 'month') {
modelToUser = function(date) {
return date.getMonth();
}
userToModel = function(month) {
ngModel.$modelValue.setMonth(month);
return ngModel.$modelValue;
}
}
else if (part == 'day') {
modelToUser = function(date) {
return date.getUTCDate();
};
userToModel = function(day) {
ngModel.$modelValue.setUTCDate(day);
return ngModel.$modelValue;
};
}
ngModel.$formatters.push(modelToUser);
ngModel.$parsers.push(userToModel);
}
}
})
和模板:
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
{{date | date}}
<input date-part-input part="year" ng-model="date">
<input date-part-input part="month" ng-model="date">
<input date-part-input part="day" ng-model="date">
</body>
您可以创建一个可重复使用的指令,其中包含三个字段,以便它可以用于所有日期字段。该指令的模型在隔离范围内别名为 date
。为了获取每个日期部分,然后拆分日期,然后将 year
、month
和 day
分配给范围属性。然后,当更改其中一个字段时,日期 属性 通过将它们与 -
分隔符一起附加来更新。
对于这个指令,我只是硬编码,年月日。我建议使用一些 javascript 日期函数来填充它们,这样它们就不会被硬编码。
angular
.module('app')
.directive('dateSelect', function (){
return {
restrict: 'E',
replace: true,
scope: {
date:'=ngModel'
},
template: '<div class="dateSelect"><div class="dateField"><selectize placeholder="Select a year..." config="yearConfig" ng-model="year" ng-change="dateChanged()"></selectize></div>' +
'<div class="dateField"><selectize placeholder="Select a month..." config="monthConfig" ng-model="month" ng-change="dateChanged()"></selectize></div>' +
'<div class="dateField"><selectize placeholder="Select a day..." config="dayConfig" ng-model="day" ng-change="dateChanged()"></selectize></div></div>',
controller: function ($scope) {
$scope.yearConfig = {
options: [{value: 2013, text: '2013'}, {value: 2014, text:'2014'}, {value: 2015, text:'2015'}],
create: true,
sortField: 'value',
maxItems: 1,
};
$scope.monthConfig = {
options: [{value: '01', text: '1'}, {value: '02', text: '2'}, {value: '03', text:'3'},
{value: '04', text: '4'}, {value: '05', text:'5'}, {value: '06', text:'6'}, {value: '07', text: '7'}, {value: '08', text:'8'}, {value: '09', text:'9'},
{value: '10', text: '10'}, {value: '11', text:'11'}, {value: '12', text:'12'}],
create: true,
sortField: 'value',
maxItems: 1,
};
$scope.dayConfig = {
options: [{value: '01', text: '1'}, {value: '02', text: '2'}, {value: '03', text:'3'},
{value: '04', text: '4'}, {value: '05', text:'5'}, {value: '06', text:'6'}, {value: '07', text: '7'}, {value: '08', text:'8'}, {value: '09', text:'9'},
{value: '10', text: '10'}, {value: '11', text:'11'}, {value: '12', text:'12'}],
create: true,
sortField: 'value',
maxItems: 1,
};
$scope.dateChanged = function () {
if (!angular.isUndefined($scope.year) && !angular.isUndefined($scope.month) && !angular.isUndefined($scope.day)) {
$scope.date = $scope.year + "-" + $scope.month + "-" + $scope.day;
}
}
if (!angular.isUndefined($scope.date)) {
var dateParts = $scope.date.split("-");
if (dateParts.length === 3) {
$scope.year = dateParts[0];
$scope.month = dateParts[1];
$scope.day = dateParts[2];
}
}
}
};
});
指令可能是最好的,但这些示例看起来过于复杂。无论如何,如果您希望避免指令,只需使用 $scope.$watch
并在每次更新其中一个重要变量时重新构建日期字符串。
您的控制器中可能有这样的东西:
$scope.year = '';
$scope.month = '';
$scope.day = '';
// this might be able to be refactored
$scope.$watch('year', buildDate);
$scope.$watch('month', buildDate);
$scope.$watch('day', buildDate);
function buildDate() {
$scope.date = $scope.year + "-" + $scope.month + "-" + $scope.day;
}
附带说明一下,这可能也是我的指令逻辑的样子。
编辑:代码清理和 fiddle
更清晰的示例 - 我更喜欢这个,因为它将所有与日期相关的项目分组到一个对象中,这也使得观察变化更容易。
$scope.date = {
year: '',
month: '',
day: ''
};
// use watch collection to watch properties on the main 'date' object
$scope.$watchCollection('date', buildDate);
function buildDate(date) {
$scope.dateString = date.year + "-" + date.month + "-" + date.day;
}