3

I'm trying to do something simple using Typescript and knockout, but can't get it to work. As my codebase of typescipt grows, it seems my viewmodels are growing and need te be nicely modeled in main classes and sub classes. Typescript is perfect for it! In combination with knockout I ran into an annoying problem/bug/situation .... Any help appreciated!!! Here's some typescript code:

class subClassA {
  counter  =0;
  incCounter(){
    this.counter++;
    console.log("counter incs: "+this.counter);
  }
}

class MainViewModel {
  a = new subClassA();


  constructor(){
    this.a.incCounter(); // this works...
  }
  incCounterIndirect(){
    this.a.incCounter(); // this works....
  }
}
ko.applyBindings(new MainViewModel() );

HTML:

<a data-bind="click: $root.incCounterIndirect ">Indirect does work</a>
<a data-bind="click: $root.a.incCounter ">Direct does NOT work</a>

Obviously I need the 'direct' route to work, ie .. calling methods on subClasses directly from the data-bind. Otherwise I need to make proxy members on the mainviewmodel for each subclass/member ...

Which binding prefix or whatever other trick could do the job of calling the member of object A from the click handler.

Any help appreciated, Paul

Paul0515
  • 23,515
  • 9
  • 32
  • 47
  • Can you be more specific? So what do you mean on does work and does not work? What should be the expected output and what happens instead? – nemesv Sep 23 '13 at 19:18
  • nemesv, from the click evt handler, i'd like to be able to call the method incCounter on subClassA directly, not using a proxy method on the main viewmodel. – Paul0515 Sep 23 '13 at 19:32

3 Answers3

5

Use instance members with the fat arrow (introduced in TS 0.9.x) to overcome this scoping issues with prototype members :

class subClassA {
  counter=0;
  incCounter= ()=>{  // Notice difference
    this.counter++;
    console.log("counter incs: "+this.counter);
  }
}

class MainViewModel {
  a = new subClassA();


  constructor(){
    this.a.incCounter(); 
  }
  incCounterIndirect= ()=>{    // Notice difference
    this.a.incCounter(); 
  }
}
ko.applyBindings(new MainViewModel() );
basarat
  • 261,912
  • 58
  • 460
  • 511
  • 1
    basarat ... that does it!!! thanx a million. nilgundag was commenting in the same direction, I jusnt couldnt figure out how to put it in typescript. Anyway ... thanx all for the great help!!!! – Paul0515 Sep 23 '13 at 23:37
2

I am not familiar with typescript but i think the answer should be similar to this:

class subClassA {
  counter  =0;
  self = this;
  incCounter(){
    self.counter++;
    console.log("counter incs: "+self.counter);
  }
}

The problem is with the "this" keyword. it gets different values in your two different versions. To ensure it always have the same value, you capture value of this keyword in the self variable and use it.

Here is fiddle for the javascript version: http://jsfiddle.net/nilgundag/ySmw3/

function subClassA() {
    this.counter  = 0;
    var self = this;
    this.incCounter = function(){
        self.counter++;
        console.log("counter incs: "+self.counter);
    }
}

function MainViewModel() {
    this.a = new subClassA();
    this.incCounterIndirect=function(){
        this.a.incCounter(); // this works....
    };
}
ko.applyBindings(new MainViewModel() );
nilgun
  • 10,460
  • 4
  • 46
  • 57
  • Thanx for your input. I tried to get this in TypeScript. self = this; (works), self.counter++; (Does not work) – Paul0515 Sep 23 '13 at 20:03
  • I see. This link may help: http://stackoverflow.com/questions/12756423/is-there-an-alias-for-this-in-typescript – nilgun Sep 23 '13 at 20:14
  • That example shows indeed the problem, but does not really come up with an elegant solution imho. Simple things are becoming quit complex like that. – Paul0515 Sep 23 '13 at 21:27
1

Rather than change your handlers to use the fat arrow, you can instead just change the way you're binding:

<a data-bind="click: $root.a.incCounterIndirect.bind($root.a)">This will work</a>

This creates a new function whose "this" argument is the view model, which causes the method to behave like an instance method.

Working JSFiddle here: http://jsfiddle.net/Vjknn/

Judah Gabriel Himango
  • 58,906
  • 38
  • 158
  • 212
  • Judah, if changing the handlers could be avoided, it would be nice indeed. The example you gave however, IMHO, does not change a lot ... your snippet does the same as:Indirect does work – Paul0515 Sep 24 '13 at 22:15
  • Sorry, I just noticed you're trying to call it on $root.a. I've updated the above code with a working example and fiddle. – Judah Gabriel Himango Sep 25 '13 at 20:09
  • Judah, thanx ... hummm that indeed solves the problem, WITHOUT changing the implementation of the handlers ... nice! I will have to figure out which solution I prefer ... – Paul0515 Sep 26 '13 at 10:00
  • Personally, I prefer to avoid messing with the code and just change the way the binding will call the handler. But, you're free to choose whichever you prefer. – Judah Gabriel Himango Sep 26 '13 at 15:59