1

Problem: I have a controller that gets an array of players, and presents them to the user. These player have a set of stats that a user can then incremented or decremented, since a player can have multiple stats, and I want them to behave the same, so I am creating a directive that has a number and 2 buttons "+" & "-". When the user clicks "+" the value should go up, when the user clicks "-" the value should go down. The goal with this directive is to make it easy to tweak the template and have that reflected everywhere, the directive is also trying to be stat agnostic, that way it can be re-used for several different stats. The user can have selectedPlayer, this directive will be bound to a stat on this selectedPlayer. The issue I am running into is if I change the selectedPlayer the directive doesn't seem to update with the new selectedPlayer, or the new value from the directive doesn't seem to actually update the selected player.

Code may help explain this better.

<div class="h3 text-center">{{title}}</div>
<button class="btn btn-lg plusMinus-btn btn-danger" ng-click="statCtrlr.statDown()">-</button>
<span class="stat-val digits md vcenter text-center" style="width: 50px;" ng-cloak>{{statCtrlr.statValue}}</span>
<button class="btn btn-lg plusMinus-btn btn-success" ng-click="statCtrlr.statUp()">+</button>

the js (.ts actually) file:

var app = angular.module("stat-val", []);

app.directive("statVal", () => {

    return {
        restrict: 'E',
        templateUrl: 'templates/statValue.html',
        //transclude:true,
        scope: {
            title: "@",
            data: "=" 
            //prop:"="
            //statValue: "=val",
            //statCol: "@col",
            //plrid: "@plrid",
            /*plr:"=plr"*/
        },
        controller: ['$scope', '$http', function ($scope, $http) {
            //$scope.statValue
            var ctrl = this;

            //ctrl.statValue = $scope.data[$scope.prop];
            console.log("$scope", $scope);


            ctrl.statValue = $scope.data;

            console.log('stat-val::$scope', $scope.data, $scope, ctrl.statValue);
            //console.log($scope.$parent.entryCtrlr.selectedPlayer.plrid);


            this.statDown = () => {
                console.log("statDown", ctrl.statValue);
                if (ctrl.statValue > 0) {
                    ctrl.statValue--;

                }
            };

            this.statUp = () => {
                console.log("statUp", ctrl.statValue);
                ctrl.statValue++;

            };
        }],
        controllerAs: 'statCtrlr'
    }
});

how is its being called in html

<div class="col-xs-3 no-gutter">
     <stat-val title="FGM" data="entryCtrlr.selectedPlayer.stats.fgm" prop="fgm"
     ></stat-val>
</div>

The json data that gets used:

player: [
{
 stats: {
  fgm: 0,
  fga: 0,
  fgm3: 0,
  fga3: 0,
  ftm: 0,
  fta: 0,
  tp: 0,
  blk: 0,
  stl: 0,
  ast: 0,
  min: "",
  oreb: 2,
  dreb: 4,
  treb: 6,
  pf: 0,
  tf: 0,
  to: 0
},

},

Update

console dump:

enter image description here

2 Answers 2

3

The critical problem is hidden here:

...
ctrl.statValue = $scope.data;
...

which is a statement happening at the controller construction phase. I.e. the $scope.data could be null at that moment... so we do NOT assign a reference to later data... just a NULL is assigned (or undefined):

// these is two way binding - but no could empty $scope.data
// it is like doing this

ctrl.statValue = null; // no reference

There is an example with async data load in parent controller (out of directive)

  .directive('statVal', function()
  {
    return {
        restrict: 'E',
        templateUrl: 'templates/statValue.html',
        //transclude:true,
        scope: {
            data: "=",
            // ...
        },
        controllerAs: 'statCtrlr',
        controller: ['$scope', '$http', function ($scope, $http) {
            //$scope.statValue
            var ctrl = this;

            //ctrl.statValue = $scope.data[$scope.prop];
            console.log("$scope", $scope);

            ctrl.statValue = $scope.data;
        }],
    };
  })

.controller("myCtrl", function($scope, $http) {
  $http.get("data.json")
    .then(function(response){
      $scope.data = response.data;
    })
})

How we can fix it? the most simple is to use a "." in the model name, to use a Model : { data }

There is updated example

The controller creates a Model

.controller("myCtrl", function($scope, $http) {
  $scope.Model = {};
  $http.get("data.json")
    .then(function(response){
      $scope.Model.data = response.data;
    })
})

and that is passed to directive

<stat-val model="Model"></stat-val> 

And directive is now assigning to its controller the Model

  .directive('statVal', function()
  {
    return {
        restrict: 'E',
        templateUrl: 'templates/statValue.html',
        //transclude:true,
        scope: {
            Model: "=model",
            // ...
        },
        controllerAs: 'statCtrlr',
        controller: ['$scope', '$http', function ($scope, $http) {
            //$scope.statValue
            var ctrl = this;

            //ctrl.statValue = $scope.data[$scope.prop];
            console.log("$scope", $scope);

            //ctrl.statValue = $scope.data;
            ctrl.Model = $scope.Model;
        }],
    };
  })

Check it here

Another way is to watch the original data... until they really come to directive, and THEN assign them to controller... But I tried to explain what is happening and the above example is more precise

NOTE: I used just JavaScript for examples, because the issue is NOT related to Typescript here

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the very good answer, however @avijit gupta answered first and it worked. so I am going to select his as the final answer, again thanks for the help!
The point is - you have the answer. That is great! Enjoy UI-Router, sir ;)
1

Your $scope.data is two-way binded, not ctrl.statValue:

this.statDown = () => {
    console.log("statDown", $scope.data);
    if ($scope.data > 0) {
        $scope.data--;
    }
};

this.statUp = () => {
   console.log("statUp", $scope.data);
   $scope.data++;
};

5 Comments

but shouldn't ctrl.statValue, just be a ref to the same thing?
ctrl.statValue is just a copy of the $scope.data, but only scope variables are two-way binded. So you would have to explicitly update $scope.data for the changes to be reflected elsewhere.
so does my html bind like this then <span>{{$scope.data}}</span>. do I still use the data:"="?
You never use $scope inside your .html, your .html would bind like this: <span>{{data}}</span>. And yes, you should still use data: "="
Yep, I got it to work! in hindsight so simple, such is things I guess. Thought now that I've gotten it working, I am thinking it may make more sense to pass a ref to function using the "&", as it could be possible in the future for stats to increment differently, etc.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.