13

I have an object that i want to do drawing on a canvas. It will use requestAnimationFrame to start a game loop:

Contoso.ts

class Contoso
{
   //private ctx: CanvasRenderingContext2D;

   Initialize(ctx: CanvasRenderingContext2D) {
      //this.ctx = ctx;
      Render();
   }

   Render() {
      //...snip doing any actual drawing for the purpose of this question
      requestAnimationFrame(this.Render);
   }
}

app.ts

var contoso: Contoso;

contoso = new Contoso();
contoso.Initialize(canvas);

The first time someone calls Initialize, the requestAnimationFrame manages to correctly call Render.

The second time requestAnimationFrame calls Render, the this.Render is undefined and it crashes.

It's almost as though the object was destroyed after the initial call to Initialize.

What is going on?

Bharata
  • 13,509
  • 6
  • 36
  • 50
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219

4 Answers4

46

You've lost this context. Two possible fixes:

class Contoso
{
   /* ... */

   // Use () => syntax so Render always gets 'this' context
   // from the class instance
   Render = () => {
      //...snip doing any actual drawing for the purpose of this question
      requestAnimationFrame(this.Render);
   }
}

The alternate fix is probably slightly clearer, but has the downside of making a lot more allocations (you probably don't want to allocate 1 closure per frame!)

   Render() {
      //...snip doing any actual drawing for the purpose of this question
      requestAnimationFrame(() => this.Render);
   }
Ryan Cavanaugh
  • 209,514
  • 56
  • 272
  • 235
  • At least I know I'm not crazy - people have had this problem before me. On the other hand, the fixed versions smell enough that I'm probably not supposed to have a class maintain the render loop. I am probably supposed to have global variables invoke the a Render method on that global variable – Ian Boyd Feb 21 '14 at 12:21
  • 2
    @Ryan: First suggestion (i.e. Render = () => { ... }) works nicely. Thanks. – omt66 Apr 19 '15 at 06:40
  • is there any need of cancelling the animation frame for the performance? if yes then please let me know how can I do it? – Umesh Patadiya Feb 04 '19 at 04:19
8

Use arrow syntax (lambda):

requestAnimationFrame(() => this.Render());
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
x0n
  • 51,312
  • 7
  • 89
  • 111
4

The best approach I've found.

requestAnimationFrame(this.Render.bind(this));

.bind(this) creates a new function that has its this keyword set to the provided value.

Bonus Reading

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Tyler V
  • 272
  • 3
  • 11
  • using `bind` is dangerous: https://basarat.gitbooks.io/typescript/docs/tips/bind.html – gregmatys Mar 12 '18 at 10:13
  • Can you expand on `bind`'s dangers, @gregmatys? The page you linked to doesn't exist at that URL any more, and a search on the site didn't (quickly) show up anything explicitly arising against binding class methods. When writing class-based React code in JavaScript, the `this.fn = this.fn.bind(this)` pattern is very common. I assume many people will encounter problems like this moving to TypeScript, so more explanation here would help. – Simon Dell May 07 '20 at 23:36
  • https://hamednourhani.gitbooks.io/typescript-book/docs/tips/bind.html – gregmatys May 08 '20 at 09:42
2

On Firefox 49.0.1 I've got an error message using Ryan Cavanaugh solution.

SyntaxError: bad method definition

for the line :

Render = ()=> {

The work around I've found looks like this :

class Test{

    constructor(){

        this.Render = ()=> {
            requestAnimationFrame( this.Render );
        };

    }
}
  • Try it with `Render = () => {` instead of `this.Render = () => {`. To be clear, I am referring to @Ryan's code, outside of a constructor. – Kent Weigel Mar 14 '18 at 00:12