0

My form data is returned from a REST call. Example data is:

{
    id: 4
    version: 3
    code: "ADSFASDF"
    definition: "asdflkj"
    value: "1234"
    active: "false"
    formula: false
    validTo: "2014-12-31T05:00:00"
    validFrom: "2010-12-31T10:00:00"
}

I'm running into trouble using input[number] and input[date] fields as they require the data to be a number or a date respectively and not a string. Similar problems occur for booleans (check boxes), etc.

I thought I could get around it using a $formattter, but the value passed to the formatter is always "". I presume this means the that $formatter is called after Angular has already tried to parse the data model.

Without having to initially cast everything in my controller, is there a way to cast the data via a directive, or within the HTML tag directly?

Ex:

<input type="number" class="form-control" ng-model="charge.value" jk-numeric id="chargeValue"/>

$formatter:

app.directive( 'jkNumeric',
    function() {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function(scope, element, attrs, ngModelCtrl) {
                var stringToNum = function(text) {
                    if( angular.isNumber(text) )
                        return text;
                    else
                        return parseInt( text, 10);
                }

                // assign the parser and the formatter to the model
                ngModelCtrl.$formatters.unshift(stringToNum);
            }
        };
    });
9
  • Why don't you just send a number from the server? Or parse it as you get it Commented Aug 22, 2014 at 19:10
  • may be the value is "" because of the model (input) is not valid Commented Aug 22, 2014 at 19:11
  • @LoganMurphy The API was designed that way as the "value" can either be a numeric input or a formula. If a formula, then it needs to be a String. Commented Aug 22, 2014 at 19:15
  • Then it is not a number. Commented Aug 22, 2014 at 19:16
  • @Whisher Not sure what you mean. The model is valid; if I switch the input type to text, I see the value properly populated Commented Aug 22, 2014 at 19:16

2 Answers 2

2

You can do this way :-

Set your directive to a higher priority so that the directive runs before ng-model does its viewValue/modelValue evaluation, and do a 2 way binding with ngModel to get the actual value you set (and not the viewValue of ngModel), and to support async data assignment keep a temporary watch. You cannot possibly do it with formatters because they run after ngModel has evaluated the value.

.directive( 'jkNumeric',
    function() {
        return {
            restrict: 'A',
            require: 'ngModel',
            priority:999999, //<-- Give a higher priority
            scope: {         
               model:'=ngModel' //<-- A 2 way binding to read actual bound value not the ngModel view value
            },
            link: function(scope, element, attrs, ngModelCtrl) {

             //Perform whatever formatting you want to do.
             function stringToNum(text) {
                  return +text;
              }

             var unwatch = scope.$watch('model', function(val){
                if(!val) return;
                ngModelCtrl.$setViewValue(stringToNum(val));
                ngModelCtrl.$render();
                unwatch();
              });

            }
        };
    });

Plnkr

Another way to watch would be to watch on evaluated value of ngModel attribute.

            require: 'ngModel',
            priority:999999,
            link: function(scope, element, attrs, ngModelCtrl) {

              function formatToNumber(text) {
                  //do something and return numeric value
              }

             scope.$watch(function(){
                 return scope.$eval(attrs.ngModel); //evaluate the scope value
             }, function(val){
                 if(!val) return;
                 ngModelCtrl.$setViewValue(formatToNumber(val));
                 ngModelCtrl.$render();
              });

Plnk2

Sign up to request clarification or add additional context in comments.

5 Comments

This is one of the best answers I've ever seen for an angularjs question I was looking at answering myself. Great stuff.
@PSL Thanks - at first looks a little convoluted, and am surprised that Angular requires this much effort for something fairly simple, but I see that Angular is like that sometimes. Why do you deregister the watch expression once it has been executed? What if the model is changed a second time async? Was that intentional or not a case you were trying to protect?
@EricB. Yeah i put that intentionally, if it is only for the first time assignment.. You can keep it if you have a refresh scenario otherwise why to keep a watch, it can be destroyed. hence.
I'm not sure I understand the full purpose of the isolated scope. Can you not just put the watch on attrs.ngModel directly? How does the isolated scope help?
@EricB. Yes you can do, but no point watching on it directly on attrs.ngModel you would need to do scope.$eval on the watch evaluation function. Providing isolated scope with 2 way, you can avoid doing that. Anyways i will update with that option as well on how to do it. Actually i generally prefer isolated scope because it is completely separated out except for the configured value.
1

Could you try something like:

app.directive( 'jkNumeric',
    function() {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function(scope, element, attrs, ngModelCtrl) {
                if (angular.isString(ngModelCtrl.$viewValue) {
                    ngModelCtrl.$setViewValue(parseInt(ngModelCtrl.$viewValue, 10));
                    ngModelCtrl.$render();
                }
            }
        };
    });

1 Comment

Very similar to @PSL's answer, however he pointed out the need to have a higher priority level.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.