Third-Party JavaScript Libraries

You can use third-party JavaScript libraries with Lightning web components. For example, use a library with interactive charts and graphs or one to reduce code complexity.

Using JavaScript to manipulate the DOM isn’t recommended because the Lightning Web Components engine does it more efficiently. However, there are some third-party JavaScript libraries that take over the DOM.

When using these libraries, add lwc:dom="manual" to any empty native HTML element that you want to manipulate with JavaScript. When the engine sees the directive, it preserves encapsulation. The owner of the component calls appendChild() on that element to manually insert the DOM.

This code is the LibsD3 component from the Lightning Web Components recipes app. It uses the D3 JavaScript library to create an interactive data visualization. The template contains the graph. The container is an empty <svg>. The lwc:dom="manual" directive tells Lightning Web Components that the DOM in this element has been inserted manually.

<!-- libsD3.html -->
<template>
    <div>
      <svg
        class="d3"
        width={svgWidth}
        height={svgHeight}
        lwc:dom="manual"
      ></svg>
    </div>
</template>

A component displaying a graph of colored dots connected by lines. To change the shape of the graph, click and drag the dots.

To initialize the graph, in renderedCallback(), the code calls initializeD3(), which reaches into the DOM and gets a reference to the container where we want to display the graph, here an <svg> element.

In a Lightning web component, you can’t use document to query for DOM elements. Instead, use this.template. For example, this code uses this.template.querySelector('svg.d3').

/* global d3 */
import { LightningElement } from 'lwc';
import DATA from './data';

export default class LibsD3 extends LightningElement {
  svgWidth = 400;
  svgHeight = 400;

  d3Initialized = false;

  renderedCallback() {
    if (this.d3Initialized) {
      return;
    }
    this.d3Initialized = true;
    this.initializeD3();
  }

  initializeD3() {
    // Example adopted from https://bl.ocks.org/mbostock/2675ff61ea5e063ede2b5d63c08020c7
    const svg = d3.select(this.template.querySelector('svg.d3'));
    const width = this.svgWidth;
    const height = this.svgHeight;
    const color = d3.scaleOrdinal(d3.schemeDark2);

    const simulation = d3
      .forceSimulation()
      .force(
        'link',
        d3.forceLink().id(d => {
          return d.id;
        })
      )
      .force('charge', d3.forceManyBody())
      .force('center', d3.forceCenter(width / 2, height / 2));

    const link = svg
      .append('g')
      .attr('class', 'links')
      .selectAll('line')
      .data(DATA.links)
      .enter()
      .append('line')
      .attr('stroke-width', d => {
        return Math.sqrt(d.value);
      });

    const node = svg
      .append('g')
      .attr('class', 'nodes')
      .selectAll('circle')
      .data(DATA.nodes)
      .enter()
      .append('circle')
      .attr('r', 5)
      .attr('fill', d => {
        return color(d.group);
      })
      .call(
        d3
          .drag()
          .on('start', dragstarted)
          .on('drag', dragged)
          .on('end', dragended)
      );

    node.append('title').text(d => {
      return d.id;
    });

    simulation.nodes(DATA.nodes).on('tick', ticked);

    simulation.force('link').links(DATA.links);

    function ticked() {
      link
        .attr('x1', d => d.source.x)
        .attr('y1', d => d.source.y)
        .attr('x2', d => d.target.x)
        .attr('y2', d => d.target.y);
      node.attr('cx', d => d.x).attr('cy', d => d.y);
    }

    function dragstarted(d) {
      if (!d3.event.active) {
        simulation.alphaTarget(0.3).restart();
      }
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }

    function dragended(d) {
      if (!d3.event.active) {
        simulation.alphaTarget(0);
      }
      d.fx = null;
      d.fy = null;
    }
  }
}