D3.js 和 Ionic 2:在第二个页面视图中不在 DOM 中的元素

D3.js and Ionic 2: Elements not in DOM on second page view

我创建了一个简单的 Ionic 应用程序来测试 d3.js 的集成。在这里能找到它: https://github.com/wberger/ionic2-d3js-test

我是这样整合的:

  1. 创建一个带有侧边导航和单页的应用程序(home.html
  2. home.html 添加到菜单。
  3. 在 index.html 中加载 d3.js: <script src="https://d3js.org/d3.v4.min.js"></script>
  4. <div id="chart"></div> 添加到 home.html
  5. home.ts
  6. 中创建 ngAfterViewInit() 中的图表

home模板定义如下(source):

<ion-header>
  <ion-navbar>
    <button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  The world is your oyster.
  <p>
    If you get lost, the <a href="http://ionicframework.com/docs/v2">docs</a> will be your guide.
  </p>
  <div id="chart"></div>
  <p>After</p>
</ion-content>

图表创建实现如下(source):

  ngAfterViewInit() {
    this.createChart();
  }

  createChart() {
    var chart = d3.select("#chart").append("svg")
      .attr("width", 100)
      .attr("height", 100)
      .append("g");

    var rows = [
      {x: 1, y: 1},
      {x: 2, y: 2},
      {x: 3, y: 3},
    ];

    var xScale = d3.scaleLinear()
            .range([0, 100])
            .domain([1, 3]);
    var yScale = d3.scaleLinear()
            .range([100, 0])
            .domain([1, 3]);

    chart.selectAll(".dot")
      .data(rows)
      .enter().append("circle")
      .attr("class", "dot")
      .attr("cx", (row) => { return xScale(row.x) })
      .attr("cy", (row) => { return yScale(row.y) })
      .attr("r", 3.5);
  }

启动应用程序时,图表会按预期显示。但是当(重新)从侧面菜单打开页面时,图表不存在。在检查 Google Chrome 中的 TS/DOM 时,我可以在重新打开时观察到以下行为:

ngAfterViewInit() 之后创建的 SVG 消失了。似乎 DOM 被替换为空的(或缓存的??)模板。

所以我的问题是:

我通过将图表构建为一个组件解决了这个问题:

@Component({
  selector: 'my-chart',
  inputs: ['options', 'data'],
  template: ``
})
export class MyChart implements OnChanges {

  el: any;
  options: MyChartOptions;
  data: MyChartData;

  chart: any;
  arcMap: any;

  /**
   * Initializes the component with the root element.
   */
  constructor(@Inject(ElementRef) elementRef: ElementRef) {
    this.el = elementRef.nativeElement;
  }

  /**
   * Handler for binding changes.
   * @param changes The changed bound values.
   */
  ngOnChanges(changes) {
    if (changes.options) {
      this.updateWithOptions(this.options);
    }

    if (changes.data) {
      this.updateWithData(this.data);
    }
  }

  // handlers, drawing etc. ...
}

然后可以整合如下:

<my-chart [data]="chartData" [options]="chartOptions"></my-chart>

不要忘记在app.module.ts中注册组件,例如:

import { MyChart } from '../components/charts/my-chart';

// ...

@NgModule({
  declarations: [
    // ...
    MyChart
  ],
  imports: [
    // ...
  ],
  bootstrap: [MyApp],
  entryComponents: [
    // ...
  ],
  providers: [
    // ...
  ]
})