2

I've been looking for a solution but couldn't find it anywhere. I am trying to create an ko.computed which is dependant on two other observables. Whenever that observable changes, I was hoping for my computed observable to also update, but it does not. Computed function is not updating(view + variable itself) when I change any of its dependencies. Also another question which could be releated to this problem. Why is "self" variable global? Even if I remove it from AppViewModel, it is still global.

Here is short version and below you can find an JSFiddle for a full working example. When you press "Attack" your Strength should increase from 5 to 1000, which in turn should update progress bar "maxExp" value from 0/6 to some high value 0/500 or something.

function AppViewModel() {
var self = this;
player = new playerTest();
enemies = new enemiesTest();
activeEnemy = new initializeFirstEnemy();
console.log('Call me more pls');
};
var baseStatsArray = ['Strength', 'Endurance', 'Speed', 'Luck'];
var currentEnemy = ko.observable(0);

function playerTest() {
   var self = this;
   var value;
   var growth;
   this.baseStats = {};
for (var i = 0; i < baseStatsArray.length; i++) {
    var attr = baseStatsArray[i];
    this.baseStats[attr] = {};
    this.baseStats[attr]['value'] = ko.observable(5);
    this.baseStats[attr]['growth'] = ko.observable(2);
    this.baseStats[attr]['minExp'] = ko.observable(0);
    this.baseStats[attr]['maxExp'] = ko.computed(function () {
        return Math.floor((10 + (self.baseStats[attr]['value']() / 2)) / self.baseStats[attr]['growth']());
    });
};
};

https://jsfiddle.net/tuswdahc/1/

Mariusz
  • 361
  • 1
  • 4
  • 17

1 Answers1

4

The problem is that you try to create your computed inside a loop and because the way closures and variable scoping working you are not creating the correct computeds.

For further info see:

Some possible solutions:

Use ko.utils.forEach or the native array.forEach instead of the for loop:

ko.utils.arrayForEach(baseStatsArray, function(attr) {
    self.baseStats[attr] = {};
    self.baseStats[attr]['value'] = ko.observable(5);
    self.baseStats[attr]['growth'] = ko.observable(2);
    self.baseStats[attr]['minExp'] = ko.observable(0);
    self.baseStats[attr]['maxExp'] = ko.computed(function () {
        return Math.floor((10 + (self.baseStats[attr]['value']() / 2)) / 
            self.baseStats[attr]['growth']());
    });
});

Move the computed creation into a separate function:

for (var i = 0; i < baseStatsArray.length; i++) {
    var attr = baseStatsArray[i];
    self.baseStats[attr] = {};
    self.baseStats[attr]['value'] = ko.observable(5);
    self.baseStats[attr]['growth'] = ko.observable(2);
    self.baseStats[attr]['minExp'] = ko.observable(0);
    self.baseStats[attr]['maxExp'] = createMaxExp(attr, self);
 };

function createMaxExp(attr, self) {
    return  ko.computed(function () {
        return Math.floor((10 + (self.baseStats[attr]['value']() / 2)) /   
            self.baseStats[attr]['growth']());
    });
 }

Demo JSfiddle.

Regarding the your problem with the "global" self: you are not correctly creating your viewmodel because you are missing the new when calling the applyBindings.

So change

 ko.applyBindings(AppViewModel);

To

ko.applyBindings(new AppViewModel());
Community
  • 1
  • 1
nemesv
  • 138,284
  • 16
  • 416
  • 359
  • Hey, your solution works great :) Thanks for your help. I still have problem with global "self" even after those changes. Even if I remove var self = this; from every place in my code, it is still global, so I assume some of my libraries could cause this? Also your fiddle code is doing ko.applyBindings(new AppViewModel); without brackets, while your answer shows to use brackets. I assume that I need those brackets when creating a new object. – Mariusz Dec 31 '15 at 16:37