3

I'm trying to make a directive that reads from two input sources and do some stuff to turn it into one. So to do that I'm listening to the changes of my two inputs and assigning the new combined value to the ngModel of my directive.

The problem is that I also need to know when the ngModel is modified outside the directive to do the reverse process and properly set the values of my two directive input sources. There is a proper way to do that?

I made a snippet to better illustrate the problem, this isn't my actual directive.

angular.module('myapp', [])

.controller('AppController', function($scope, $timeout) {
  $scope.data = {
    value: ''
  };
  
  $timeout(function() {
    $scope.data.value = 'hellooooo';
  }, 5000);
})

.directive('sampleDirective', function() {
  return {
    require: 'ngModel',
    restrict: 'E',
    template: '<input ng-model="data.input1" ng-change="changed()"><input ng-model="data.input2" ng-change="changed()">',
    scope: {
      ngModel: '='
    },
    controller: function($scope) {
      $scope.data = {};
      $scope.changed = function() {
        $scope.ngModel = $scope.data.input1 + ' + ' + $scope.data.input2;  
      }
      
      // The watch is running even when the ngModel is modified inside the changed function above 
      // I want it to only run when the model is changed from outside the directive, like
      // I'm doing in the AppController.
      $scope.$watch('ngModel', function(value) {
        if (value) {
          // Just simulating some processing
          var length = value.length;
          
          $scope.data.input1 = value.slice(0, length/2);
          $scope.data.input2 = value.slice(length/2, length-1);
        }
      });
    }
  };
});
<div ng-app="myapp">
  
  <div ng-controller="AppController">
    <sample-directive ng-model="data.value"></sample-directive>

    <br>
    <br>
    <div>{{ data.value }}</div>
  </div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
Hodes
  • 895
  • 2
  • 10
  • 18
  • A different solution for your problem using the ngModel controller is presented here: http://stackoverflow.com/questions/25141773/angularjs-bind-one-ng-model-to-directive-with-two-inputs – Numyx Jul 28 '15 at 06:12

2 Answers2

3

What you have there is a custom input control. It is an input control that has 2 textboxes, but the "model" is really for a combination of both.

To implement a custom input control that "supports" ngModel, i.e. integrates with other directives that require: ngModel, your directive also needs to require: ngModel and use the ngModelController hooks to integrate.

So, don't just do scope: { ngModel: "=" }. With this, you are two-way-binding to a model attached to the ng-model attribute, but things would not work as you expect them when you set this value with scope.ngModel = "something".

Angular provides an example of a custom input control in its documentation of ngModelController. In a nutshell, you will need to orchestrate setting the $viewValue - when the underlying View changes, and setting the View - when the model changes.

.directive("sampleDirective", function(){
  return {
    require: "ngModel",
    scope: true,
    template: '<input ng-model="d.input1" ng-change="viewChanged()">\
               <input ng-model="d.input2" ng-change="viewChanged()">',

    link: function(scope, element, attrs, ngModel){
      var d = scope.d = {};      

      ngModel.$render = render;

      scope.viewChanged = read;


      // defines how to render based on model changes
      function render(){
        var modelValue = ngModel.$modelValue || "";
        var length = modelValue.length;

        d.input1 = modelValue.slice(0, length/2);
        d.input2 = length > 1 ? modelValue.slice(length/2, length) : "";
      };


      // defines how to set the model based on DOM changes
      function read(){
        var newViewValue = d.input1 + d.input2;
        ngModel.$setViewValue(newViewValue);
      }
    }
  };
});

Then, your control would work with any other directive that supports ngModel, like required or ng-pattern or ng-change, and could participate in form validations:

<div ng-form="form1">
  <sample-directive ng-model="foo" maxlength="8" ng-change="doSomething()">
  </sample-directive>
</div>

Demo

New Dev
  • 48,427
  • 12
  • 87
  • 129
0

Rather strange requirement but you can change your directive to use the ngModelController instead

.directive('sampleDirective', function() {
  return {
    require: 'ngModel',
    restrict: 'E',
    template: '<input ng-model="data.input1" ng-change="changed()"><input ng-model="data.input2" ng-change="changed()">',
    scope: {
      ngModel: '='
    },
    link: function(scope, element, attributes, ngModelCtrl) {
      // This will be called when it is changed from outside the directive
      ngModelCtrl.$formatters.push(function(value) {
        if (value) {
          // Just simulating some processing
          var length = value.length;

          scope.data.input1 = value.slice(0, length/2);
          scope.data.input2 = value.slice(length/2, length-1);
        }
        return value;
      });

      scope.changed = function() {
        // Set the model value from inside the directive
        ngModelCtrl.$setViewValue(scope.data.input1 + ' + ' + scope.data.input2);
      }
    },
    controller: function($scope) {
      $scope.data = {};
    }
  };
})
logee
  • 5,017
  • 1
  • 26
  • 34