Skip to content

docs: add a guide for using Components#204

Open
Fevol wants to merge 9 commits intoobsidianmd:mainfrom
fevol-forks:component-guide
Open

docs: add a guide for using Components#204
Fevol wants to merge 9 commits intoobsidianmd:mainfrom
fevol-forks:component-guide

Conversation

@Fevol
Copy link
Contributor

@Fevol Fevol commented Nov 9, 2025

This is a preliminary PR for adding a guide on how to use the Component class for managing lifecycles and events.

This guide aims to give developers a tool to avoid common mistakes made during plugin development, including:

  • Adding event listeners on window, document, view, ..., but not removing them up when the plugin/view/... is unloaded
  • Instantiating resources from libraries, WASM or classes, but never deallocating them
  • Attaching elements to the DOM, without removing them
  • Creating a Component for a MarkdownRenderer.render call, but never attaching it to a parent Component

Any input would be very welcome:

  • Location (is this guide in a discoverable location?)
  • Title (does the title make sense, should the document be split up?)
  • Examples (do they make sense, are they correct, ...)
  • Etc...

Comment on lines +163 to +167
temporarilyUnloadWidget() {
// You can also unload it temporarily if needed
this.widget.unload();
window.setTimeout(() => this.widget.load(), 5000);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is a good example for plugins. Usually Components should not be calling load/unload themselves. If they are properly attached to a parent, then the parent Component is responsible for loading and unloading

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed this example for now. The main idea behind this is that you can fully unload and re-load the component, and everything will be handled properly.

Comment on lines +40 to +45
window.addEventListener("resize", onResize);
}

onunload() {
window.removeEventListener("resize", onResize);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is awkward since onResize is undefined and its very easy to mess up removeEventListener (if you don't pass in the exact instance, it won't be properly removed).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if there is a different, but still simple, example that can be used for this. For now, I have changed the example such that onResize is a method on the class.

}
```

While this works, it quickly becomes repetitive and error-prone. Fortunately, Obsidian's `Component` system offers a better solution.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a little awkward since the example with onload and onunload is already using functions of Component.

Copy link
Contributor Author

@Fevol Fevol Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for the purposes of this example, that is fine. The main point to demonstrate here is that explicitly registering something to the Component is safer, rather than handling the loading/unloading yourself (be it in a Plugin, a custom class that you manage the lifecycle of, etc...). The Plugin just happens to be the most well known construction for this 😅

What do you think of:

While this works fine, you need to remember to remove your listener in the onunload call. Obsidian fortunately offers a better and simpler methods to register events via the Component system:

createEl("button", { text: "Click me!" })
);
// Good
button.addEventListener("click", onButtonClick);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Obsidian native approach for these would be:

new ButtonComponent(this.containerEl).setButtonText('Click me!').onClick(() => {
	new Notice('Button clicked!');
});

// keyboard events
this.scope = new Scope(this.app.scope); //this does not need to be unloaded, Obsidian takes care of it
this.scope.register(['Ctrl'], 's', () => {
	new Notice('Shortcut pressed!');
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally chose this construction with button and window to showcase the parallels for registering listeners, and the difference in how long these listeners would live for.

Do you think this should be added as a separate example? Or maybe as an additional note clarifying that there is a more correct implementation available using Obsidian API's.

…hitespaces)

docs: remove passive voice constructions
docs: improve comments for codeblocks
Copy link
Contributor

@liamcain liamcain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Very informative, and easy to follow.


### Solution: Add or use an existing component

Instead, you should pass a component that has a clearly defined lifecycle, one that will be loaded and unloaded properly. Since _every_ view is a `Component`, we can simply pass the `View` instance itself via `this`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might want to mention that using the plugin / app instance as the compoent is a bad idea here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current phrasing is clear enough. Here's my attempt to add more info:

Instead, you should pass a component that has a clearly defined lifecycle, one that will be loaded and unloaded at an appropriate time. Both Plugin and View are components; however, View is a more appropriate choice since its lifespan matches with the MarkdownRenderer. We can simply pass the View instance itself via this:

Might a bit wordy though...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't even considered that people might use plugin or app... (is App a component though?) Regardless, I think this is an useful addition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the suggestion!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants