Angular directive change ngmodel value from null to undefined breaks validation

Issue

I have created a plnkr to describe my problem: Link to plnkr

Problem description:
I have a number field, the value from which is written to the model. First I implemented this feature like the first input. The problem with this implementation is that if I input something and then delete, I have the following model:

{"firstNumber":null,"secondNumber":64}

For me unfortunately this representation is not acceptable, I need the following result:

{"secondNumber":64}

For that I taken the directive and implemented the second field. Now I receive the correct output, but when I remove the value, the form becomes invalid.

Also I added third and fourth input to demonstrate that the directive also breaks the required validation.

So, the question is:
How can I improve the input field not to have the model

{"firstNumber":null,"secondNumber":64}

but

{"secondNumber":64}

and do not broke the validation mechanism of Angular forms.

For reference:
I have the following HTML:

<body ng-controller="MainCtrl as vm">
<h1>Validating input inside ng-repeat with Angular 1.3</h1>

<form name="vm.myForm" novalidate>
  <input type="number" ng-model="vm.fields.firstNumber" name="firstNumber">
  <input type="number" ng-model="vm.fields.secondNumber" name="secondNumber" null-to-undefined>
  <br>
  <input type="number" ng-model="vm.fields.thirdNumber" name="thirdNumber" ng-required = "true">
  <input type="number" ng-model="vm.fields.fourthNumber" name="fourthNumber" null-to-undefined ng-required="true">
</form>
</body>

And following controller and directive code:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  var vm = this;

  vm.fields = {};
  vm.fields.firstNumber = 12;
  vm.fields.secondNumber = 24;
  vm.fields.thirdNumber = 64;
  vm.fields.fourthNumber = 128;

});

app.directive('nullToUndefined', function($timeout) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, elem, attrs, ctrl) {
      ctrl.$parsers.push(function(viewValue, modelValue) {
        if (viewValue === null) {
          $timeout(function() {
            //ctrl.$setValidity('number', true);
          });
          return undefined;
        }
        return viewValue;
      });
    }
  };
});

p.s. I cut the code as much as possible to reproduce the problem from my main project. I would appreciate any solution, but it would be very cool, if somebody can suggest the solution, which:
1. Shows good performance
2. Behaves in angular way.

Solution

You should probably focus on other solution, instead of writing directive.

You have object with props (firstNumber, secondNumber, …).

You need to validate property with Angular (is null ? is correct number ?).

Then, you need to filter the object props with non-null values, without mutation of your model : your input is bind to model property, if you destroy a property, Angular can’t be able to validate and fail.

We can do it :

  • Select all keys of a.fields
  • Iterate all keys, filters
  • We got all non-null keys
  • Reduce non-null keys to a new object, without mutation of “a”
const a = {
  fields: {
    a: 1,
    b: 2,
    c: null,
  },
};

// After form validation
const newA = Object
  .keys(a.fields)
  .filter(k => a.fields[k] !== null)
  .reduce(
    (accumulator, k) => 
      Object.assign(
        accumulator,
        { 
          fields: Object.assign(accumulator.fields, { [k]: a.fields[k] }) 
        }
      ),
    { fields: {} }
  );

And if you need to filter undefined + null, just do little check on filter :

  .filter(k => a.fields[k] != null)

Edit : fix code.

Answered By – Robin Meillet

Answer Checked By – Jay B. (AngularFixing Admin)

Leave a Reply

Your email address will not be published.