Relm
Relm

Reputation: 8287

Typescript "this" instance is undefined in class

I found this and online and now trying to put it in TS.

Running the following throws Uncaught TypeError: Cannot set property 'toggle' of null

@Injectable()
export class HomeUtils {
    private canvas: HTMLCanvasElement;
    private context;
    private toggle = true;

    constructor() { }

    public startNoise(canvas: HTMLCanvasElement) {
        this.canvas = canvas;
        this.context = canvas.getContext('2d');
        this.resize();
        this.loop();
    }

    private resize() {
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;
    }

    private loop() {
        this.toggle = false;
        if (this.toggle) {
            requestAnimationFrame(this.loop);
            return;
        }
        this.noise();
        requestAnimationFrame(this.loop);
    }

    private noise() {
        const w = this.context.canvas.width;
        const h = this.context.canvas.height;
        const idata = this.context.createImageData(w, h);
        const buffer32 = new Uint32Array(idata.data.buffer);
        const len = buffer32.length;
        let i = 0;

        for (; i < len;) {
            buffer32[i++] = ((255 * Math.random()) | 0) << 24;
        }

        this.context.putImageData(idata, 0, 0);
    }

}

I'm lost.

Upvotes: 29

Views: 25846

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250366

Methods do not capture this and are dependent on the caller to call them with the correct this. So for example:

this.loop() // ok
let fn = this.loop;
fn(); // Incorect this
fn.apply(undefined) // Undefined this

Since you pass loop to another function requestAnimationFrame you need to ensure that this is captured from the declaration context and not decided by requestAnimationFrame :

You can either pass an arrow function to requestAnimationFrame

private loop() {
    this.toggle = false;
    if (this.toggle) {
        requestAnimationFrame(() => this.loop());
        return;
    }
    this.noise();
    requestAnimationFrame(() => this.loop());
} 

Or you can make loop an arrow function not a method:

private loop = () => {
    this.toggle = false;
    if (this.toggle) {
        requestAnimationFrame(this.loop);
        return;
    }
    this.noise();
    requestAnimationFrame(this.loop);
}

The second approach has the advantage of not creating a new function instance on each call to requestAnimationFrame, since this will be called a lot, you might want to go with the second version to minimize memory allocations.

Upvotes: 42

Oscar Paz
Oscar Paz

Reputation: 18312

It's the call to requestAnimationFrame. You are passing a function not bound to a context, and so, inside that call to loop there is no this.

Change the call to:

requestAnimationFrame(() => this.loop());

Arrow functions, contrarily to normal functions, are bound to this.

Upvotes: 9

Related Questions