Is it possible to render simple mathematics formulas using katex?
The katex.render() javascript API call requires a DOM element be passed to it, but the DOM element doesn't seem to exist as such in TemplateResult, and katex errors out:
render() {
return html`
<section>
<p id="formula">${katex.render("c = \\pm\\sqrt{a^2 + b^2}", document.getElementById("formula"),
{throwOnError: true})}</p>
</section>
`;
}
Uncaught (in promise) TypeError: Cannot set property 'textContent' of null
at Object.Mn [as render] (katex.min.js:1)
at HTMLElement.render (my-view1.js:35)
at HTMLElement.update (lit-element.ts:170)
at HTMLElement.performUpdate (updating-element.ts:224)
at HTMLElement._enqueueUpdate (updating-element.ts:712)
Mn @ katex.min.js:1
render @ my-view1.js:35
update @ lit-element.ts:170
performUpdate @ updating-element.ts:224
_enqueueUpdate @ updating-element.ts:712
async function (async)
_enqueueUpdate @ updating-element.ts:708
requestUpdate @ updating-element.ts:687
set @ updating-element.ts:356
_attributeToProperty @ updating-element.ts:640
attributeChangedCallback @ updating-element.ts:584
(anonymous) @ my-view1.js:44
The function is being evaluated before the dom even exists, you would want to render the template, then use the third party function after.
Thanks. I'm using the PWA Starter Kit, and I think I see a good spot in app.js:
const loadPage = (page) => (dispatch) => {
switch(page) {
case 'view1':
import('../components/my-view1.js').then((module) => {
// Put code in here that you want to run every time when
// navigating to view1 after my-view1.js is loaded.
});
break;
...
Apparently that is only the loading step and doesn't guarantee that page has been rendered. Could someone point me to the proper lifecycle hook in lit-element where I can run this function? (firstUpdated(...) doesn't seem to do it either.)
firstUpdated should be the right place unless it gets overwritten on update, then you could use updated to have it rerun on every update.
I think the problem is that if you are using what you showed above, you would need to change from targeting document and instead use this.shadowRoot for the selector. (From inside whatever element is the template)
I would not suggest manually changing the DOM inside of a template handled by lit-html, as lit will overwrite when there are changes.
Easiest would be to create a small web component wrapper:
import { katex } from 'katex';
class KatexElement extends HTMLElement {
set formula(formula) {
if (formula === this._formula) {
return;
}
this._formula = formula;
katex.render(formula, this, { throwOnError: true });
}
}
customElements.define('katex-element', KatexElement);
To use it:
<katex-element .formula=${'c = \\pm\\sqrt{a^2 + b^2}'}></katex-element>
This implementation also effectively directly manipulates DOM that is rendered by lit-html. You would have to use a custom element with a ShadowRoot to avoid doing that.
What makes the solution nicer is that it is declarative, so whenever lit-html renders it will most likely produce the desired outcome. (Although this implementation still breaks in some edge cases)
Thanks for all your thoughtful responses. Sorry I borked using document instead of the ShadowRoot. It's strange to be working with sandboxed code in JS of all languages! I am no longer getting the error, but there are some rendering issues to work through still. I definitely like the idea of wrapping the library with a proper web component as the longer term solution.
FYI, I have a couple of katex elements here: https://github.com/justinfagnani/katex-elements
@justinfagnani Thanks so much for the reference to your code! I have another noob question: Why not subclass LitElement when wrapping katex?
Most likely because LitElement did not exist at the time the project was created.
Even if it did, the project is so simple that LitElement isn't really needed here. Adding dependencies to a project add all sorts of costs. Even aside from the obvious cost of needing more code to work, in this example the project would need to be updated every time LitElement is updated.
Every choice in programming is a tradeoff. Dependencies add a cost. If they don't add significant benefit, they are probably not worth the cost.
@markcampanelli: @ruphin's right, it just didn't need it. As long as the text content is the only state that renders, LitElement wouldn't really do anything.
What the Katex elements really need to be robust is a MutationObserver to handle changes to the text content. It might be a better design to move to a single element with an attribute to control display vs inline. _Then_ is might start making some sense to use a helper, but probably not before.
@ruphin @justinfagnani Thanks for the info. I'm having good success with lit-html and LitElement thus far as I make the transition from Polymer2.
Most helpful comment
FYI, I have a couple of katex elements here: https://github.com/justinfagnani/katex-elements