最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to bind one model to multiple inputs with Angular JS - Stack Overflow

programmeradmin1浏览0评论

I have an form input which is for a MySQL date field. Ex: 2015-01-31.

I want to allow the user to input this using 3 different form inputs. One for the year, one for the month, and one for the day.

Obviously ng-model isn't going to work right out of the box, because I'm trying to bind just one part of the date string to each input. I'm pretty sure the way to do it is bye creating three "temporary" scope vars/models

$scope.year;
$scope.month;
$scope.day;

...and then somehow combine/binding them to the actual value.

//If only it were this easy!
$scope.date = $scope.year + "-" + $scope.month + "-" + $scope.day;

The line above of course won't work because the values aren't two-way-bound. If the form were only for saving new data, I could could get away with that by just combining the inputs on submit. But I need it to handle/show existing data also. And it's going to get super ugly if I can't figure out a way to wrangle Angular's binding magic to do what I want.

I found 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 directive, which adds an additional layer of complexity to that approach.

I have an form input which is for a MySQL date field. Ex: 2015-01-31.

I want to allow the user to input this using 3 different form inputs. One for the year, one for the month, and one for the day.

Obviously ng-model isn't going to work right out of the box, because I'm trying to bind just one part of the date string to each input. I'm pretty sure the way to do it is bye creating three "temporary" scope vars/models

$scope.year;
$scope.month;
$scope.day;

...and then somehow combine/binding them to the actual value.

//If only it were this easy!
$scope.date = $scope.year + "-" + $scope.month + "-" + $scope.day;

The line above of course won't work because the values aren't two-way-bound. If the form were only for saving new data, I could could get away with that by just combining the inputs on submit. But I need it to handle/show existing data also. And it's going to get super ugly if I can't figure out a way to wrangle Angular's binding magic to do what I want.

I found 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 directive, which adds an additional layer of complexity to that approach.

Share Improve this question edited May 23, 2017 at 12:24 CommunityBot 11 silver badge asked Jan 25, 2015 at 3:34 emersonthisemersonthis 33.4k60 gold badges222 silver badges383 bronze badges 15
  • The simplest solution is to have 3 scope properties and then combine them. When you fetch the date form your api, you will need to split the field up and set each scope property. You can create a re-usable directive to do this so it can be used in more than one place – Wayne Ellery Commented Jan 25, 2015 at 3:47
  • I think a reusable directive that contains the three fields and converts from/to a single date field should be the way to go. But why don't you use a date picker such as ui.bootstrap.datepicker ? – ps0604 Commented Jan 25, 2015 at 3:47
  • @WayneEllery is it going to be messy to create a directive and still use angular-selectize? – emersonthis Commented Jan 25, 2015 at 3:53
  • @ps0604 As I said in my question, I agree that a directive is probably "better" but I just don't know how to do it. And I suspect it will be tricky to integrate with angular-selectize. If it's not as hard to do as I think I'm open to it. I'll leave the datepicker discussion for another time. – emersonthis Commented Jan 25, 2015 at 3:59
  • It's not that tricky, you would just use angular-selectize in your directive instead of using a normal select. Without a directive it's much simpler but using a directive is recommended so that it can be re-used. – Wayne Ellery Commented Jan 25, 2015 at 4:10
 |  Show 10 more comments

3 Answers 3

Reset to default 11

A directive is probably best, but these examples look overly complex. Anyway if you are hoping to avoid a directive, just use $scope.$watch and re-build your date string each time one of the important variables are updated.

Something like this might be in your controller:

$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;
}

As a side note, this is probably what my directive logic would look like too.

Edit: Code cleanup and fiddle

Cleaner example - I prefer this because it groups all the date-related items with an object, which also makes watching for changes easier.

$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;
}

Fiddle

here's an interesting demo that uses custom directives that are a lot less intimidating than the ones you linked to. You should be able to apply them to your inputs without too much conflict with other stuff:

http://plnkr.co/edit/3a8xnCfJFJrBlDRTUBG7?p=preview

The trick is setting the parser and formatter for a model using the directive. This lets you intercept changes to the model and interact with the rest of your scope:

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);
    }
  }
})

And the template:

<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>

You can create a re-usable directive that has the three fields in it so it can be used for all date fields. The model for the directive is aliased to date on the isolated scope. To get each of the date parts the date is then split and then year, month and day are assigned to scope properties. Then when one of the fields is changed the date property is updated by appending them together with the - separator.

For this directive I've just hard coded, years months and days. I'd recommend to use some javascript date functions to populate them so they aren't hard coded.

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];
          }
        }
      }
    };
});

Plunkr

发布评论

评论列表(0)

  1. 暂无评论