Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind one model to multiple inputs with Angular JS

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.

like image 416
emersonthis Avatar asked Jan 25 '15 03:01

emersonthis


2 Answers

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

like image 64
tydotg Avatar answered Oct 03 '22 00:10

tydotg


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>
like image 20
Peter Ashwell Avatar answered Oct 03 '22 01:10

Peter Ashwell