Sign in to follow this  
Nomid

TypeScript: cannot set property of undefined

Recommended Posts

Hello, this is my first topic on this board.

I'm coding a simple BreakOut clone in TypeScript.
My app consists of a main BreakOut class and the instances for Ball, Paddle and the canvas context.
 

BreakOut.ts

/**
 * Created by Nomid on 20/01/2016.
 */

/// <reference path="GameObject.ts"/>
/// <reference path="Ball.ts"/>
/// <reference path="Paddle.ts"/>
/// <reference path="Direction.ts"/>
/// <reference path="Sprite.ts"/>
/// <reference path="Key.ts"/>

class BreakOut
{
    Ball : Ball;
    Paddle : Paddle;

    private pressed_keys : { [keycode: number] : boolean };

    update(time: number): void
    {
        this.context.fillStyle = "red";
        this.context.fillRect(0, 0, this.context.canvas.width, this.context.canvas.height);

        this.Ball.update(time);
        this.Paddle.update(time, this.pressed_keys);
    }

    updateKeys(E: KeyboardEvent) {
        this.pressed_keys[E.which || E.keyCode] = !this.pressed_keys[E.which || E.keyCode];
    }

    constructor(public context: CanvasRenderingContext2D) {
        this.Ball = new Ball(context);
        this.Paddle = new Paddle(context);

        this.pressed_keys = [];

        window.addEventListener("keypress", this.updateKeys);
        window.addEventListener("keyup", this.updateKeys);
    }
}

It compiles without errors, but when I run it, these errors appear:

screenshot_234.png

BreakOut is initialized as follows:

// jQuery 2.2.0
$(document).ready(function()
{
    var Canvas = $("<canvas/>")
            .attr("width", window.innerWidth)
            .attr("height", window.innerHeight)
            .appendTo("body");
    window.Game = new BreakOut(Canvas[0].getContext("2d"));

    window.requestAnimationFrame(Game.update);
});

Thank you for helping me.

Share this post


Link to post
Share on other sites

You pass the context into the constructor but never assign it to the object.

A quick `this.context = context` in the constructor should solve it (not that I know much about ts mind).

Oh, just saw the second error. You'll still need the context thing above but you'll need to bind those methods. I'm not sure of the specifics but it looks like TS classes are the same as regular JS classes and stuff in JS-land does not autobind methods to classes. Either of the following will work:

window.addEventListener( "keypress", this.updateKeys.bind( this ) )

// or

this.updateKeys = this.updateKeys.bind( this )
window.addEventListener( "keypress", this.updateKeys )

There are subtle differences in those approaches, namely the bottom one will ensure that the function is always bound whereas the 1st passes only the bound function as the event handler, if you call the function elsewhere it will not be bound.

If TS supports class properties there is a third way to bind class members.

---

In a different vein, using jQuery for that use is totally bonkers, I guess maybe you're using it elsewhere...

Share this post


Link to post
Share on other sites
11 minutes ago, mattstyles said:

You pass the context into the constructor but never assign it to the object.

A quick `this.context = context` in the constructor should solve it (not that I know much about ts mind).

I read that when specifying constructor(public member : any), member becomes a public property of the object and is auto-assigned (from typescriptlang official).

For the bindings, the argument is passed as the listener works, the problem is that pressed_keys is seen as undefined in that context :(

Share this post


Link to post
Share on other sites
16 minutes ago, Nomid said:

I read that when specifying constructor(public member : any), member becomes a public property of the object and is auto-assigned (from typescriptlang official).

For the bindings, the argument is passed as the listener works, the problem is that pressed_keys is seen as undefined in that context :(

We can't be expected to know what you've read (also despite your capitalisation Google suggests it has not seen that exact sequence of words).

You titled the thread "TypeScript: cannot set property of undefined" and detailed two errors (by screenshot) only the first of which was setting a property of a member you haven't declared which Matt reasonably suggested you should fix...

As for why pressed_keys might be giving you problems, isn't it an array of length 0, what do you expect the 100th element to be??!

Share this post


Link to post
Share on other sites
8 minutes ago, chg said:

We can't be expected to know what you've read (also despite your capitalisation Google suggests it has not seen that exact sequence of words).

You titled the thread "TypeScript: cannot set property of undefined" and detailed two errors (by screenshot) only the first of which was setting a property of a member you haven't declared which Matt reasonably suggested you should fix...

screenshot_235.png

This is what I read from here: http://www.typescriptlang.org/Handbook#classes, maybe did I misunderstand it?

Anyway Matt's solution is ok, I was just wondering why the constructor didn't create the member itself, sorry for that.

Quote

As for why pressed_keys might be giving you problems, isn't it an array of length 0, what do you expect the 100th element to be??!

So it doesn't work just as javascript does.
In JavaScript you declare objects like this: var obj = {};

I thought that an accessor like obj[prop] could be fine in TypeScript too.

Share this post


Link to post
Share on other sites
Quote

For the bindings, the argument is passed as the listener works, the problem is that pressed_keys is seen as undefined in that context :(

Bound or not the function will execute, it'll just be in the context of the window rather than the scope being bound to the object, which the inspector is telling you, and `pressed_keys` doesnt exist (undefined) on the window.

 

Share this post


Link to post
Share on other sites
1 hour ago, mattstyles said:

Bound or not the function will execute, it'll just be in the context of the window rather than the scope being bound to the object, which the inspector is telling you, and `pressed_keys` doesnt exist (undefined) on the window.

 

Understood, so the scope in my code is switched to the window object. Thank you!

window.addEventListener("keypress", this.updateKeys.bind(this));
window.addEventListener("keyup", this.updateKeys.bind(this));

Worked :)

 

chg and Matt were very close to the solution of the "undefined" issue.
https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript

After reading this extract
screenshot_238.png

screenshot_239.png

 

So, to make it work, I did:

public update = (time: number) => {
    this.context.fillStyle = "red";
    this.context.fillRect(0, 0, this.context.canvas.width, this.context.canvas.height);

    this.Ball.update(time);
    this.Paddle.update(time, this.pressed_keys);
};

Thanks to all for your help!!

Edited by Nomid
Solution is provided

Share this post


Link to post
Share on other sites
Quote

If TS supports class properties there is a third way to bind class members.

Looks like it does support class properties. The actual spec can be found here.

Any of the ways proposed are a solution. JS being heavily event-driven means that, in difference of most classical languages (JS is not classical, its not really a class you are defining, not in the traditional sense anyway), event handler are scoped to the element that invokes them. When JS adopted the class syntax there was a lot of discussion about whether the scope should be classical or JS-style, it was fairly unanimous that JS-style should remain. Many libraries, such as React's createClass method, goes against spec and autobinds.

You're using a class property/field and an arrow function to get scope how you want it. If you look at the transpiled version you'll see whats happening (arrow functions are supported in most browsers but not all, I expect the standard TS transpilation will support older browsers, so it'll bind rather than rely on the arrow). A quick word of warning on using arrow functions like this, they mutate scope in a fairly unique way and it probably isnt how you think, more info here

Another, very brief note on class properties, you're defining a function for each new class to get the bound scope. Mostly this doesnt matter but it is worth considering. If you create 1000 instances then thats 1000 functions, normally JS creates just the 1 and references it from the prototype.

Share this post


Link to post
Share on other sites

Yes, I read about that after finding the solution.

Anyway, I took a look at the resulting JS, finding it interesting how it is structured.

This is because, technically JS is not OOP, since prototypes only emulate the way we're used to instance objects.

 

It was very clear, thank you

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.