3

Question

Is it possible to pass a scope variable (specifically an array) by reference to a function called using ng-click, and manipulate the value of said variable?

Update: To be more explicit, my goal here is to avoid having to access $scope in $scope.app.onItemClick.

Example

Javascript:

(function() {
    angular.module('test', [])
    
    angular.module('test').controller('test',['$scope', function ($scope) {
      $scope.app = {};
      
      $scope.app.primarySet = [
        'test1',
        'test2',
        'test3'
      ]
      
      $scope.app.workingSet = [
        'test4',
        'test5',
        'test6'
      ]
      
      /*
       * Attempts to assign $scope.app.primarySet to $scope.app.workingSet using references to those two values.
       */
      $scope.app.onItemClick = function (primarySet, workingSet) {
        primarySet = workingSet
      }
    }])
  })()

Relevant HTML:

<button type="button" ng-click="app.onItemClick(app.primarySet, app.workingSet)">Update Primary Set</button>

Please see this Plunker for this code in more context.

My expectation for this code is that $scope.app.primarySet would be set to $scope.app.workingSet when the button is pressed. Unfortunately, this is not the case. While debugging, in the scope of the function, primarySet is assigned to workingSet. However $scope.app.primarySet is not.

Motivation

My motivation for this is rooted in this SO reply. I share the belief that it will be easier to test methods that manipulate scope if I am not referencing it directly. I also find this more straightforward than having a function manipulate the scope directly.

Previous Research

The only resource that I have come across relating to this is this SO question. While this question comes close, it is different in that the parameter in question is a string, which, if I understand correctly, cannot be modified as a reference could be expected to.

Community
  • 1
  • 1
jmdarling
  • 120
  • 2
  • 7
  • did you tried: – Pablo Ortuño Jan 20 '16 at 17:54
  • @oxigenao, yes, that does work, but I don't find it to be a good long term solution as that mapping shouldn't be in the template and isn't testable. – jmdarling Jan 20 '16 at 17:59
  • oxigenao is correct in their suggestion, when dealing with primatives as an inheritance it is suggested as a best practice to use '.' The prototypical inheritance you are working with is a result of javascript and not angular. https://github.com/angular/angular.js/wiki/Understanding-Scopes offers some good information on scope and how you can navigate through them – rlcrews Jan 20 '16 at 18:09
  • goal to not use `$scope.app` still doesn't explain what you are trying to do with this function at all. Why are you not wanting to modify scope for testing in the first place? – charlietfl Jan 20 '16 at 18:14
  • @charlietfl, this function is the best abstraction I could come up with to a much larger concern in a much larger app. My motivation for not wanting to directly modify scope for testing is an attempt to be able to test just the function without any side effects and without having to mock scope. That and in my larger project that this represents, I have so many references to scope that it is getting downright confusing. While this may be a sign of poor code structure on my part, I am trying to find a pattern that enforces it. I hope that helps clear it up a bit. – jmdarling Jan 20 '16 at 18:23

2 Answers2

1

JavaScript allows you to access properties of objects as keys, so you would modify your template as follows:

<button type="button" ng-click="app.onItemClick(app, 'primarySet', app, 'workingSet')">Update Primary Set</button>

And modify the onItemClick method to:

  $scope.app.onItemClick = function ( leftObject, leftProperty, rightObject, rightProperty) {
    leftObject[leftProperty] = rightObject[rightProperty];
  }
Mark
  • 1,128
  • 13
  • 21
  • While this works, it really is a nightmare what comes to maintenance and understanding what is happening :) – Mikko Viitala Jan 20 '16 at 18:09
  • In most cases I would totally agree! This adds a lot of flexibility which isn't required in the toy example. I am however expecting the OP has a good set of supporting reasons for requiring this flexibility and will factor out unneeded complexity. – Mark Jan 20 '16 at 18:15
1

You can change the primarySet in $scope if you pass the $scope.app trough onItemClick, like this:

  (function() {
    angular.module('test', [])

    angular.module('test').controller('test',['$scope', function ($scope) {
      $scope.app = {};

      $scope.app.primarySet = [
        'test1',
        'test2',
        'test3'
      ]

      $scope.app.workingSet = [
        'test4',
        'test5',
        'test6'
      ]

      /*
       * Attempts to assign $scope.app.primarySet to $scope.app.workingSet using references to those two values.
       */
      $scope.app.onItemClick = function (app, workingSet) {
        app.primarySet = workingSet;
      }
    }])
  })()

<button type="button" ng-click="app.onItemClick(app, app.workingSet)">Update Primary Set</button>

And the reason is when you passed the array only the value was passed to the function.