NgModel - how to manage the original cost of a model without making it messy?

Let's say I need a directive where it takes what the user enters into an input and turns it into a lowercase one.

So I came up with a directive like this (and in this Plunker http://plnkr.co/edit/jnE3s8MRr1tFCX0WVYel?p=preview ):

app.directive('lowerCaseInput', function() {
  return {
    restrict: 'E',
    template:'<input ng-model="vm.input" />',
    scope: {},
    require: ['ngModel', 'lowerCaseInput'],
    controller:function() {},
    link: function(scope, el, attrs, ctrls) {
      var ngModel = ctrls[0], vm = ctrls[1];
      ngModel.$render = render;

        // view-> model
      ngModel.$parsers.push(function(value) {
        return toLowerCase(value);
      });

        // model->view
      ngModel.$formatters.push(function(value) {
        return value;
      });

      function render() {
        vm.input = ngModel.$viewValue;
      }

      // view -> model && model-> view
      function toLowerCase(value) {
        return value && value.toLowerCase();
      }

      scope.$watch('vm.input', function(newVal, oldVal) {
        if(newVal !== oldVal)
          ngModel.$setViewValue(newVal);
        });
    },
    controllerAs: 'vm'
  }
})

      

However, as you can see in the demo, the model value is not reflected (accessed all inline) on the initial pass because setViewValue never gets fired. Is there a general approach I should follow for situations like this where I want a directive to change the model based on some rules?

Also, I would like to prevent the control from getting dirty until the user has interacted with it, so even if I got it to call $ setViewValue and call the $ parsers pipeline, I would like it to understand that the user doesn't actually interact with it (thus keeping its $ pristine).

Is it possible? If so, what are their best methods (for example, is it okay to call $ setViewValue from $ format - rewrite ngModel directly? Etc ...

+3


source to share


3 answers


The following solution worked for mine (using Angular 1.4)

Before updating viewvalue y, just keep the reference to the $ setDirty function and replace it in ngModelCtrl with an empty function. After setting the viewvalue, I return the original function.

var tmp = ngModelController.$setDirty;
ngModelController.$setDirty = angular.noop;
ngModelController.$setViewValue(value);
ngModelController.$setDirty = tmp;

      



This worked for me as I can set a default in my custom directive and both the input field and the form controller remain $ pristine.

Beware: I am not using ng-model tunable options. I haven't tried it, but I'm pretty sure if you are using a custom debug timeout this solution won't work for you, but I think it will be possible to call the line ngModelController.$setDirty = tmp;

after the debug timeout has elapsed.

+3


source


Check this panel .

app.directive('lowerCaseInput', function() {
  return {
    restrict: 'A', // as attribute to 'decorate' normal input
    require: 'ngModel',
    link: function(scope, el, attrs, ngModel) {
      // view-> model
      ngModel.$parsers.push(toLowerCase);
      // model->view
      ngModel.$formatters.push(toLowerCase);

      function toLowerCase(value) {
        return value && value.toLowerCase();
      }

      // if input value should reflect modelValue
      el.on('blur', function () {
        if (ngModel.$viewValue !== ngModel.$modelValue) {
          ngModel.$setViewValue(ngModel.$modelValue);
          ngModel.$render();
        }
      });
    }
  }
})

      

First of all: you overcomplicated your solution because you created two instances of ngModel, while you could easily decorate normal text input. In your implementation, you assigned all your logic for the ngModel to the directive element, not the ngModel input from your template.

My solution doesn't need to manipulate the ngModel in the link step, because that job is left over for $ formatters. But if you really need to manipulate the cost of the model without doing ngModel $dirty

, you can do something like:



var isPristine = ngModel.$pristine;
ngModel.$setViewValue(something);
if (isPristine) { ngModel.$setPristine(); }

      


Edit:

My solution is configured $viewValue

correctly, but $modelValue

there is still a wrong value underneath . More correct solution here , but it is far from "normal" IMO.

0


source


This is what you need for the directive.

app.directive('lowerCaseInput', function() {
  function toLowerCase(value) {
    return value && value.toLowerCase();
  }

  return {
    restrict: 'E',
    template: '<input ng-model="getterSetter" ng-model-options="{getterSetter : true}">',
    scope: {
      ngModel: '='
    },
    controller: function($scope) {
      $scope.getterSetter = function(value) {
        return $scope.ngModel = toLowerCase(value || $scope.ngModel);;
      }
    }
  }
})

      

Check Plunker here and Documentation here

0


source







All Articles