2

It seems that when I use a service, called by a controller, within a directive, to update an array in the main controller (MyController in the example), Angular doesn't update the view.

However if I leave the service out, and have the directive directly update the array, then the view is updated.

(In the example below I have, in MyDirectivesController there are 2 lines, one commented out which is the version that works - changing gridInDirective directly....the line below it calls myService, which does get the gridInDirective array locally, and does seem to change it locally, but the view is not updated)

If you run the example the idea is that you click on the colored div and the elements of the array, printed by the ng-repeat loop, are changed.

I have experimented with using $scope.$apply() in a few places but (a) didn't work and (b) my understanding is that that should only be necessary if I am making changes outside of Angular...and I don't think I am.

So the question is, how do I get the version of the grid that is updated in myService to update the view, and also why doesn't it work the way I have it?

Thanks in advance to anyone who takes the time to have a look.

'use strict';

(function() {
    var ssheet = angular.module('ssheet', []);
    
    ssheet.factory('myService', [function () {
        var myServiceFunction;
        
        return {
            myServiceFunction: function (grid) {
                alert('myServiceFunction');
                alert('grid given to myServiceFunction is ' + grid.toString());
                grid = ['now', 'the', 'grid', 'has', 'changed'];
                alert('grid in myServiceFunction changed to ' + grid.toString());
            } 
        }
    }]);
    
    ssheet.controller('MyController', ['$scope', function ($scope) {
        $scope.gridInMyController = ['foo','bar','dog','cat']; // this was a 2D array, but simplified it for testing
    }]);
    
    ssheet.controller('MyDirectivesController', ['myService', '$scope', function (myService, $scope) {
        $scope.changeGrid = function () {    
            alert('MyDirectivesController.changeGrid');
            //$scope.gridInDirective = ['now', 'the', 'grid', 'has', 'changed']; //this worked
            myService.myServiceFunction($scope.gridInDirective); // this doesn't work
        }
    }]);
    
    ssheet.directive('gridTools', ['myService', function (myService) {       
        return {
            
            restrict: 'E',
            replace: true,
            scope: {
                gridInDirective: '=gridInElement',
            },
            template: '<div class="grid-tools-style" grid-in-element="gridInMyController" ng-click="changeGrid()">Click this div to change the grid</div>',
            
            controller: 'MyDirectivesController',
        }
    }]);
    
})();
.grid-tools-style {
    background-color: #c68700;
}

.grid-tools-style:hover {
    cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="ssheet">
    <div ng-controller="MyController">
        <grid-tools></grid-tools>
        <div ng-repeat="item in gridInMyController"><p>{{item}}</p></div>
    </div>
</body>
Simon
  • 265
  • 2
  • 5

3 Answers3

0

You can return the new grid from your directive controller method and assign it to the scope variable of the grid of your directive.

See the demo below and in this jsfiddle.

'use strict';

(function() {
  var ssheet = angular.module('ssheet', []);

  ssheet.factory('myService', [
    function() {
      //var myServiceFunction;

      return {
        myServiceFunction: function(grid) {
          //alert('myServiceFunction');
          //alert('grid given to myServiceFunction is ' + grid.toString());
          grid = ['now', 'the', 'grid', 'has', 'changed'];
          //alert('grid in myServiceFunction changed to ' + grid.toString());
          return grid;
        }
      }
    }
  ]);

  ssheet.controller('MyController', ['$scope',
    function($scope) {
      $scope.gridInMyController = ['foo', 'bar', 'dog', 'cat']; // this was a 2D array, but simplified it for testing
    }
  ]);

  ssheet.controller('MyDirectivesController', ['myService', '$scope',
    function(myService, $scope) {
      $scope.changeGrid = function() {
        //alert('MyDirectivesController.changeGrid');
        //$scope.gridInDirective = ['now', 'the', 'grid', 'has', 'changed']; //this worked
        $scope.gridInDirective = myService.myServiceFunction($scope.gridInDirective); // this doesn't work
        console.log($scope.gridInDirective);
      }
    }
  ]);

  ssheet.directive('gridTools', ['myService',
    function(myService) {
      return {

        restrict: 'E',
        replace: true,
        scope: {
          gridInDirective: '=gridInElement',
        },
        template: '<div class="grid-tools-style" grid-in-element="gridInMyController" ng-click="changeGrid()">Click this div to change the grid</div>',

        controller: 'MyDirectivesController',
      }
    }
  ]);

})();
.grid-tools-style {
  background-color: #c68700;
}
.grid-tools-style:hover {
  cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="ssheet">
  <div ng-controller="MyController">
    <grid-tools></grid-tools>
    <div ng-repeat="item in gridInMyController">
      <p>{{item}}</p>
    </div>
  </div>
</div>
AWolf
  • 8,770
  • 5
  • 33
  • 39
  • Thanks! That was it (and as Omri Aharon said it was because myService.myServiceFunction($scope.gridInDirective) passed in gridInDirective by value, not by reference) – Simon Mar 28 '15 at 21:40
  • You're welcome. For pass-by-reference there is a question with some good answers here at SO. See [this question](http://stackoverflow.com/questions/7744611/pass-variables-by-reference-in-javascript). – AWolf Mar 28 '15 at 21:51
0

The reason it doesn't work is that you're passing the reference of the array by value to the myServiceFunction function, which means you can modify its contents, but can't change its reference to a new array, which is essentially what this line does:

grid = ['now', 'the', 'grid', 'has', 'changed'];

The reason it works in the directive is because you have the actual reference available to you, and you can change it to point to something else.

Omri Aharon
  • 16,959
  • 5
  • 40
  • 58
0

This is how you can keep the reference:

I have written a short function that let you put data in Object without replace it. You can use it in you service. This way youu don't need to use watch. You can use the normal digest loop of Angular. For example, see this simple controller & service:

Demo: JSFidde - http://jsfiddle.net/y09kx7f7/1/ with assert tests

Controller:

app.controller('dem',function($scope,me){
    $scope.user=me.user;   // {name:'user name'}
})

The controller will be chnage automatically without watch every time the user name changed!

Service

.factory('me',function($timeout){
    var obj={}
    obj.user={name:'hello'}
    $timeout(function(){  //Example of change the data
        newUser={name:'israel'}
        cloneInto(obj.user,newUser)
    },1000)
    return obj
})

The cloneInto function that I written:

// The function it is like a=b, 
// but keeping the object in his original memory position.
function cloneInto(a,b){
    var i
    for (i in a){
        if(typeof a[i]!='object') //For supporting deep-copy
           delete a[i]
    }
    for (i in b){
        if (typeof b[i]=='object'){
            if(!a[i]) a[i]={}
            cloneInto(a[i],b[i])
        }
        else{
            a[i]=b[i]
        }
    }
}
Aminadav Glickshtein
  • 23,232
  • 12
  • 77
  • 117