Search code examples
angularjsangularjs-directiveangularjs-ng-modelangularjs-componentsangularjs-ng-change

Use ng-model to avoid $digest delay in Component Directives


How to call controller's any function in directive in angularJs

I'm trying to call parent controller's function, which has not much to do with the directive, when directive's bind value has changed.

I've been looking through the Q&A in stack, but all of them are related to passing a value from directive to controller, which is not my case.

I thought my case was generally common, the scenario is that: display complex objects in a loop, one of property of object bind on a directive. and when the property has changed, do-something with the updated complex object.

However in my code, it prints the old object value instead of updated one. My guess is that my update function is called before $digest process has completed.

any suggestions?

plunker example: https://plnkr.co/edit/bWGxEmJjNmiKCkRZTqOp?p=preview

here is the Controller code:

  var $ctrl = this;
  $ctrl.items = [
      {id: 1, value: 10}, {id: 2, value: 20}, {id: 3, value: 30}
  ];

  $ctrl.update = function(item){
    //trying to update item after changing
    console.log("updated item is: " + angular.toJson(item));
  };

Here is the directive code:

var controller = function($scope){
   var $ctrl = this;
   $ctrl.clicked = function(){
      $ctrl.value = $ctrl.value + 1;
        
      //calling the bind on-change function defined in controller
      $ctrl.onChange();
   }
};

return {
   restrict: 'E',
   scope: {
          value: '=',
          onChange: '&'
   },
   controller: controller,
   controllerAs: '$ctrl',
   bindToController: true,
   templateUrl: 'directive.html'
};

and Html looks like this:

<div ng-repeat="item in mc.items">
  <my-directive value="item.value" on-change="mc.update(item)"></my-directive>
</div>

and directive template:

<button type="button" ng-click="$ctrl.clicked()">{{$ctrl.value}}</button>

Solution

  • You are correct that the update function is called before $digest process has completed.

    One approach is to update the value with ng-model and ng-change:

    <my-directive ng-model="item.value" ng-change="mc.update(item)">
    </my-directive>
    

    JS:

    $ctrl.value++;
    $ctrl.ngModelCtrl.$setViewValue($ctrl.value);
    

    With this approach, the ngModelController will update the value before calling ng-change.

    The Demo

    angular.module('myApp', [])
    .controller('myController', function(){
      var $ctrl = this;
      $ctrl.items = [
          {id: 1, value: 10}, {id: 2, value: 20}, {id: 3, value: 30}
      ];
    
      $ctrl.update = function(item){
        //trying to update item after changing, but here it prints out old item instead of updated item.
        console.log("updated item is: " + angular.toJson(item));
      };
      
    })
    .directive('myDirective', function(){
        var controller = function(){
          var $ctrl = this;
          $ctrl.clicked = function(){
            $ctrl.value++;
            $ctrl.ngModelCtrl.$setViewValue($ctrl.value);
            console.log("calling clicked in directive and value is ", $ctrl.value);
          }
        };
    
        return {
            restrict: 'E',
            require: {ngModelCtrl: 'ngModel'},
            scope: {
              value: '<ngModel',
            },
            controller: controller,
            controllerAs: '$ctrl',
            bindToController: true,
            template: `<button type="button" ng-click="$ctrl.clicked()">
                       {{$ctrl.value}}
                       </button>`
        };
    });
    <script src="//unpkg.com/[email protected]/angular.js"></script>
    <body ng-app='myApp' ng-controller="myController as mc">
          <div ng-repeat="item in mc.items">
            id={{item.id}}
            <my-directive ng-model="item.value" ng-change="mc.update(item)">
            </my-directive>
          </div>
    </body>


    I don't quite understand those two lines:

    1. require: {ngModelCtrl: 'ngModel'}. does ngModelCtrl works as renaming 'ngModel' so that it can be used in directive's controller?
    2. value: '<ngModel' . what does '<' do?

    require: {ngModelCtrl: 'ngModel'} binds the ng-model controller API to the directive controller with the name ngModelCtrl.

    scope: {value: '<ngModel'} creates a one-way binding of the ng-model attribute to the controller with the name value.

    For more information, see


    Using ng-model enables integration with the validation API of the ngFormController. One can create custom form components with multiple inputs.

    For more information, see