74

I hope I haven't missed anything obvious in the doco, if I have I'm sure someone will help.

I'm using asp.net webapi to return a DTO, with date fields. These are serialized using JSON.Net (in format '2013-03-11T12:37:38.693').

I'd like to use a filter but in an INPUT element, is this possible or should I create a new filter or directive to accomplish this?

// this just displays the text value
<input ui-datetime type="text" data-ng-model="entity.date" /> 
// this doesn't work at all
<input ui-datetime type="text" data-ng-model="{{entity.date|date:'dd/MM/yyyy HH:mm:ss a'}}" /> 
// this works fine
{{entity.date|date:'dd/MM/yyyy HH:mm:ss a'}}

Is there any shortcut I'm missing?

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
leon.io
  • 2,779
  • 1
  • 18
  • 26

6 Answers6

131

In short: if you want your data to have a different representation in the view and in the model, you will need a directive, which you can think of as a two-way filter.

Your directive would look something like

angular.module('myApp').directive('myDirective', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModelController) {
      ngModelController.$parsers.push(function(data) {
        //convert data from view format to model format
        return data; //converted
      });

      ngModelController.$formatters.push(function(data) {
        //convert data from model format to view format
        return data; //converted
      });
    }
  }
});

HTML:

<input my-directive type="text" data-ng-model="entity.date" /> 

Here is a working jsFiddle example.

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
holographic-principle
  • 19,688
  • 10
  • 46
  • 62
  • 5
    Looks good, but how would you apply it? Specifically, how to specify a filter to use in formatting? – Paul Taylor Aug 07 '13 at 07:52
  • @PaulTaylor restrict this directive to 'attribute' only, and use that attribute on an input field (similar to how you write ``. – Aditya M P Nov 19 '13 at 21:15
  • Also check out [this answer](http://stackoverflow.com/a/15253892/443219) for a fuller example. – Aditya M P Nov 19 '13 at 21:16
  • Working example on JSFiddle? – ViniciusPires Jul 02 '14 at 18:18
  • Thanks! The only thing your example was lacking for my solution was watching the model for changes and re-running the formatter. I didn't understand how could I achieve that easily with AngularJS, I had to force the element update: ``scope.$watch(attrs.ngModel, function(value){ element.val( $filter('number')(value, 0) ); });`` – ViniciusPires Jul 03 '14 at 12:10
  • You should not have to do that. Just defining the formatter function should be enough, unless you are updating your model from outside the scope of angular, in which case you should trigger `$scope.$apply()` – holographic-principle Jul 03 '14 at 17:43
  • this is only half of the needed code, for example, where should I put this return function? – jao Aug 26 '14 at 17:12
20

Having different values in your input field and in your model goes against the very nature of ng-model. So I suggest you take the simplest approach and apply your filter inside the controller, using a separate variable for formatted date, and employing watchers to keep formatted and original dates in sync:

HTML:

<input ui-datetime type="text" data-ng-model="formattedDate" />

JS:

app.controller('AppController', function($scope, $filter){

  $scope.$watch('entity.date', function(unformattedDate){
    $scope.formattedDate = $filter('date')(unformattedDate, 'dd/MM/yyyy HH:mm:ss a');
  });

  $scope.$watch('formattedDate', function(formattedDate){
    $scope.entity.date = $filter('date')(formattedDate, 'yyy/MM/dd');
  });

  $scope.entity = {date: '2012/12/28'};

});
Stewie
  • 60,366
  • 20
  • 146
  • 113
  • 6
    I believe creating a new directive to fulfill the requirements is a cleaner solution here. If I understood the OP's problem correctly. – holographic-principle Mar 11 '13 at 19:10
  • You're right. It is cleaner. I just wanted to give @leon an option, in case he/she is not so comfortable with custom directives yet. – Stewie Mar 11 '13 at 19:31
  • Thanks guys, both answers appreciated as it's great to learn alternative methods. – leon.io Mar 14 '13 at 16:23
  • 3
    -1, two variable + watching is a support hell. Imagine a scope with dozens of variables (insurance calculators etc) – Valentin V Sep 18 '13 at 14:57
16

If your input only displays data

If you actually need an input to simply display some information and it is some other element that changes the Angular model you can make an easier change.

Instead of writing new directive simply DO NOT USE the ng-model and use good, old value.

So instead of:

<input data-ng-model={{entity.date|date:'dd/MM/yyyy HH:mm:ss'}}" /> 

This will do:

<input value="{{entity.date|date:'dd/MM/yyyy HH:mm:ss'}}" /> 

And works like a charm :)

Atais
  • 10,857
  • 6
  • 71
  • 111
3

Complete example that formats numbers, inserting spaces every 3 characters, starting from the end:

'use strict'
String::reverse = ->
  @split('').reverse().join('')

app = angular.module('app', [])
app.directive 'intersperse', ->
  require: 'ngModel'
  link: (scope, element, attrs, modelCtrl) ->
    modelCtrl.$formatters.push (input) ->
      return unless input?
      input = input.toString()
      input.reverse().replace(/(.{3})/g, '$1 ').reverse()
    modelCtrl.$parsers.push (input) ->
      return unless input?
      input.replace(/\s/g, '')

Usage:

<input ng-model="price" intersperse/>

Plunkr example: http://plnkr.co/edit/qo0h9z

Valentin V
  • 24,971
  • 33
  • 103
  • 152
  • What exactly does modelCtrl.$parsers(or formatters).push does? – Vincent Sep 24 '13 at 09:57
  • 1
    @Vincent, it adds a function to the array of formatters that is invoked when value from model needs to be displayed. Think of of $formatters array as a pipeline filled with functions where each function is called and the result is passed further through the pipeline. Once all functions are called, the final result is displayed. The $parsers is a similar thing but for writing to model. – Valentin V Sep 24 '13 at 13:39
  • I don't think this actually works, do you have a plunker or similar? – mattcole Sep 26 '13 at 02:14
  • 1
    Nice! The only problem is that it adds a space before the number in any length multiple of 3, and the right approach would be adding the space only when the length is multiple of 3 AND has another number after it. It doesn't look like a problem when your locale doesn't define the number separator as a dot (`.`), it looks like this: `.999.999` – ViniciusPires Jul 02 '14 at 19:10
  • PS.: Working with `$filter('number');` seemed to be a better way, it loads the format through the `$locale` service, and you can use `new RegExp("\\"+$locale.NUMBER_FORMATS.GROUP_SEP, 'g');` to reverse the value to the model... – ViniciusPires Jul 03 '14 at 12:14
0

Angular has built in date format functionality, but to apply it to an input where you'll eventually like to get the raw (unformatted) date, you need to create a custom directive.

Example Directive:

(function () {
    'use strict';

    angular.module('myApp').directive('utcDate', ['$filter', function ($filter) {
        return {
            restrict: 'A', //restricting to (A)ttributes
            require: 'ngModel',
            link: function (scope, elem, attrs, model) {
                if (!model) return;

                var format = 'MM/dd/yyyy h:mm:ss a';
                var timezone = 'UTC';

                //format the date for display
                model.$formatters.push(function (value) {
                    //using built-in date filter
                    return $filter('date')(value, format, timezone);
                });

                //remove formatting to get raw date value
                model.$parsers.push(function (value) {
                    var date = Date.parse(value);
                    return !isNaN(date) ? new Date(date) : undefined;
                });
            }
        };
    }]);
})();

Then to apply it:

<input type="text" ng-model="$ctrl.DateField" utc-date />
pistol-pete
  • 1,213
  • 17
  • 13
-3

You wouldn't need to create a new filter from zero, since angular has already a built-in filter for date types. http://docs.angularjs.org/api/ng.filter:date

I believe that's exactly what you need.

odiseo
  • 6,754
  • 2
  • 20
  • 21
  • 8
    -1, OP was asking about the specific problem of using a filter with `ngModel`, which is drastically different from creating a new filter. – Valentin V Sep 18 '13 at 14:59