0

I want to create a navigation component for my project. The shell fetches a json with chapter info, these are passed to nav-element, which recursively calls itself to render the navigation tree.

shell.js

import { LitElement, html, css } from 'lit-element';
import {until} from 'lit-html/directives/until.js';
import './nav-element.js';

export class Shell extends LitElement {
  static get properties() {
    return {
      configjson : { type: Array }
    };
  }

  constructor() {
    super();
    this.configjson = fetch('./src/convertjson_test.json').then(res => res.json());
  }

  render() {
    return html`
        <main>
            some content
            <nav-element .chapters=${until(this.configjson, [])} root></nav-element>
        </main>
    `;
  }
}
customElements.define('shell', Shell);

nav-element.js

import { LitElement, html, css } from 'lit-element';
import {until} from 'lit-html/directives/until.js';
import {repeat} from 'lit-html/directives/repeat.js';

export class NavElement extends LitElement {
  static get properties() {
    return {
        chapters: {type: Array},
        root: {type: Boolean} //to mark the root node
    };
  }

  static get styles() {
    return css`
        .navheader {
            display: none;
        }

        .navheader[active] {
            display: block;
        }
    `;
  }

  render() {
    return html`
        <div class="navHeader" ?active="${this.root}">header</div>
        ${until(repeat(this.chapters, (chapter) => chapter.pos, (chapter) => html`<div>${chapter.n}<nav-element .chapters=${chapter.c}></nav-element></div>`))}
    `;
  }
}
customElements.define('nav-element', NavElement);

The problem is, that the configjson Promise is passed as property and not yet resolved by the time the nav-element is called, so i get the error:

Uncaught (in promise) TypeError: this.chapters is undefined

Searched all lit-element and lit-html documentation, the until directive resolved the issue in the shell, but not in the nav-element. The same coding pattern worked fine in Polymer 2 (&3, although with ajax instead of fetch). Does anyone know how to solve this using lit-element only?

Umbo
  • 3,042
  • 18
  • 23

1 Answers1

0

There is a time frame between the construction of NavElement and the assignment of the chapters property where chapters is undefined. It might be safe to initialize chapters in the component itself rather than in Shell's until directive, or at least provide a fallback value in the template:

export class NavElement extends LitElement {
  static get properties() {
    return {
        chapters: {type: Array},
        // ...
    };
  }

  constructor() {
    super();
    this.chapters = [];
  }

  // or

  render() {
    return html`
      ...
      ${repeat(this.chapters || [], chapter => ...)}
    `;
  }
}

Also, you've (correctly) declared the chapters property as an Array but you're wrapping the repeat directive in an until (as if it was a Promise I guess?). Here there are two things going on:

  • the repeat() call returns a DirectiveFn and not a Promise like until expects. If chapters was a Promise, the correct way to combine until and repeat would have been:

    until(
      this.chaptersPromise.then(chapters => repeat(chapters, chapter => html`...`)),
      html`Loading...`,
    )
    

    but...

  • chapters is not a Promise: the until call in the parent component resolves it and passes the result.

As for the Shell component: until used in this way should work, however its intended use is to

Render placeholder content until the final content is available

[from lit-html docs]

To make the most of it, use it to temporarily render a loading template instead of <nav-element>:

render() {
  return html`
    ${until(
      this.configJson.then(chapters => html`<nav-element .chapters=${chapters}></nav-element>`),
      html`Loading chapters`,
    )}
  `;
}

Also, not a big deal, but here the configJson property is declared as an Array but is actually a Promise.

Umbo
  • 3,042
  • 18
  • 23