29

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.

2

2 Answers 2

42

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.

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

3 Comments

Ok, this was gonna trouble me I see, thanks proper expl.
What if my loop() function has a time argument: loop(time: number)?
"Methods do not capture this" -- what a great language
9

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.

Comments

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.