简体   繁体   中英

Dynamically adding/removing Angular components to/from the DOM using D3

I'm working on an Angular component MyGraphComponent that is supposed to display a graph using D3, where the graph is given by some listOfEdges: Edge[] and some listOfNodes: Node[] that are passed to MyGraphComponent as @Input s. Every individualNode in listOfNodes is supposed to be displayed on the canvas as an Angular component <my-node [node]="individualNode"> that provides functionality for the given node instance. (Imagine eg a database schema editor where the nodes are the tables that can be edited.)

Now, I would like to have full control over how and when the nodes are added to the canvas (ie the graph's DOM) and, for this reason, I can't just use <my-node *ngFor="let node of listOfNodes"> in my graph component's template. (For instance, I would like to add animations and these animations need to be synchronized with corresponding animations of the edges and other parts of the graph for which D3 is being used.) Put differently, I need to generate all instances of MyNodeComponent on the fly, so that I can then add them to the DOM (and later remove them, if necessary) using D3.

I have found plenty of sources that explain how to generate Angular components dynamically (see eg 1 , 2 , 3 , 4 and, in particular, 5 ) but at the end they all use Angular's API to add these components to a ViewContainer . What I would like to do, however, is use D3:

function generateAngularComponent(node: Node): MyNodeComponent {
    // Use Angular's component factory to dynamically create 
    // an instance of MyNodeComponent here and return it. See 
    // the links for code examples.
}

let nodes: Node[] = [node1, node2, node3, ...];
d3.select(".my-canvas").selectAll(".my-node").data(nodes).enter()
    .append(node => {
        let component = generateAngularComponent(node);
        // Somehow return the DOM of the component here, so that 
        // D3 adds it to the canvas
    });

How do I do this – without breaking my MyNodeComponent , its change detection, dependency injection and so on? Is this possible at all?

Yes, its possible. I created a sunburst-chart for my application. I created a chart component with its type and encapsulated it in a module. Here is the structure -

 |charts.module.ts
 ├──sun-burst/
 |   ├──sun-burst-chart.component.ts
 |   ├──sun-burst-chart.component.html
 |   ├──sun-burst-chart.component.scss
 ├──some-other chart/
 |   ├──...ts

Here is my sample chart component. I have stripped unnecessary details.

import { Component, Input, ElementRef, Renderer, OnChanges } from '@angular/core';
import * as d3 from 'd3';    
export class SunburstChartComponent implements OnChanges {
        // inputs
        // objects related to chart like svg
        private htmlElement: HTMLElement;
        public constructor(public renderer: Renderer, public el: ElementRef) {
            this.htmlElement = el.nativeElement;
        }

        public ngOnChanges() {
             // remove previously added element when data changes
            d3.select(this.htmlElement).selectAll('svg').remove();
            this.displayChart();
        }
        public displayChart() {
            // this requires some additional work before chart can be prepared, refer d3 documentation for your component 
            this.svg = d3.select(this.htmlElement).append('svg')
                .attr('width', this.width)
                .attr('height', this.height)
                .append('g')
                .attr('transform', 'translate(' + this.width / 2 + ',' + (this.height / 2) + ')');

        }
        // handle additional functions required for your chart like click, tween etc., refer samples for your component from d3 website.
        private click(d, arc) { 
        }
      }

You'll need to keep references of certain elements you add inside your d3 component for manipulation. Its different based on which component you are creating.

I hope this helps. If you face any issues, create a sample on stackblitz and share the same.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM