'this' is undefined

Hi

New user here. I am trying to make a plugin. This plugin will use Cytoscape.js.

In my plugin I initialize a graph and then try to render it. But I have noticed that if I left a view open which my plugin renders in it throws a bunch of errors and doesn’t render when I restart Obsidian. If I close the view and reopen it, it renders correctly. I.e. something during startup isn’t initializing as I assume.

I then put the graph initialization in a callback given to Workspace.onLayoutReady(callback) under the assumption that whatever needed to be initialized during startup was initialized.

During my testing I noticed that I got some weird ‘undefined’ results where I wouldn’t expect ‘undefined’. After more testing this is the minimum viable example I can come up with:

plugin.ts:

import { ItemView, WorkspaceLeaf } from "obsidian";

export const VIEW_TYPE_PLUGIN = "plugin-view";
export class PluginView extends ItemView {
    constructor(leaf: WorkspaceLeaf) {
        super(leaf);

        console.log("constructor:", this);
    }

    getViewType(): string { return VIEW_TYPE_PLUGIN; }

    getDisplayText(): string { return "Plugin View"; }

    async onOpen() {
        console.log("onOpen:", this);
        this.app.workspace.onLayoutReady(this.onLayoutReady);
    }

    onLayoutReady() {
        console.log("onLayoutReady:", this);
    }
}

PluginView is instantiated in main.ts:

import { Plugin } from 'obsidian';
import { PluginView, VIEW_TYPE_PLUGIN } from 'plugin';

export default class MyPlugin extends Plugin {
    async onload() {
        this.registerView(
            VIEW_TYPE_PLUGIN,
            (leaf) => new PluginView(leaf)
        );

        this.addRibbonIcon('graph-glyph', 'My Plugin', (evt: MouseEvent) => {
            this.activateView(evt.ctrlKey);
        });
    }

    onunload() {}

    async activateView(ctrlKey: boolean) {
        await this.app.workspace.getLeaf(ctrlKey).setViewState({type: VIEW_TYPE_PLUGIN, active: true});
    }
}

The three console.log statements produce the following:

So in the constructor this is a PluginView and also in onOpen. But inside the callback it’s undefined… why?

I found this Stack Overflow post: javascript - Why is "this" in an anonymous function undefined when using strict? - Stack Overflow
but I do use new: (leaf) => new PluginView(leaf)

Why is this the case?

Fixed it myself:

Changed:

this.app.workspace.onLayoutReady(this.onLayoutReady);

to:

this.app.workspace.onLayoutReady(() => this.onLayoutReady());

Because this.onLayoutReady is defined as a function, it gets this when called.

function foo(){}

foo(); // this === undedined

obj.foo = foo;
obj.foo(); // this === obj

When you pass a function as a callback parameter, it has no this when called.

However, if you define it as a closure (arrow function), it captures its environment, including current this(PluginView), so the second method works.

So if you want to ensure a function use a specific this when passed as a callback, use func.bind(this) to force it to use the this you give.