Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Issue with Angular 2 template when using d3 #496

Closed
Gimly opened this issue Dec 7, 2016 · 6 comments
Closed

Issue with Angular 2 template when using d3 #496

Gimly opened this issue Dec 7, 2016 · 6 comments

Comments

@Gimly
Copy link

Gimly commented Dec 7, 2016

Hello,

Hopefully this question is not too off topic, but I'm getting a really weird issue with the yo aspnetcore-spa template when trying to use d3.js with it. The issue seems to be somehow linked to something in the template because the same code doesn't have the behaviour in an Angular CLI project.

I'm using d3.js to display a simple bar chart I found as an example online. It's a component that imports d3 and uses it to add an SVG and create the bar chart. When I access it through a link I added to the template's left menu, everything works fine, the chart is displayed just fine. But, if I try to access it directly through the URL I've configured as route for the component, I get this exception thrown:

Exception: Call to Node module failed with error: TypeError: Cannot read property 'createElementNS' of undefined
at Object.<anonymous> (C:\sources\d3NetCoreAngular2\ClientApp\dist\main-server.js:9352:33)
at Object.<anonymous> (C:\sources\d3NetCoreAngular2\ClientApp\dist\main-server.js:10129:39)
at Selection.selection_select [as select] (C:\sources\d3NetCoreAngular2\ClientApp\dist\main-server.js:9561:53)
at Selection.selection_append [as append] (C:\sources\d3NetCoreAngular2\ClientApp\dist\main-server.js:10128:18)
at BarchartComponent.createChart (C:\sources\d3NetCoreAngular2\ClientApp\dist\main-server.js:474:39)
at BarchartComponent.ngOnInit (C:\sources\d3NetCoreAngular2\ClientApp\dist\main-server.js:460:15)
at AppView._View_BarchartComponent_Host0.detectChangesInternal (BarchartComponent_Host.ngfactory.js:28:86)
at AppView.detectChanges (C:\sources\d3NetCoreAngular2\ClientApp\dist\vendor.js:12652:19)
at AppView.detectContentChildrenChanges (C:\sources\d3NetCoreAngular2\ClientApp\dist\vendor.js:12670:24)
at AppView.detectChangesInternal (C:\sources\d3NetCoreAngular2\ClientApp\dist\vendor.js:12662:19)

I'm really lost as to why there would be a difference between accessing the component through a link or directly. Could it be because of a server-side pre-rendering? Is there something else in the template that would make a difference between the two ways of accessing? I've tried adding a breakpoint in the typescript, if I access through the link, the breakpoint is triggered and the code stopped, but if I access directly (or if I refresh the page), then the breakpoint is not triggered and I get the exception.

Here are the important parts of the component's typescript:

...
@Component({
    selector: 'barchart',
    template: require('./barchart.component.html'),
    styles: [require('./barchart.component.css')],
    encapsulation: ViewEncapsulation.None
})
export class BarchartComponent implements OnInit, OnChanges, AfterViewInit {

    @ViewChild('chart')
    private chartContainer: ElementRef

    ...

    ngOnInit() {
        this.htmlElement = this.chartContainer.nativeElement;
        this.host = d3.select(this.htmlElement);
        let svg = this.host.append('svg')
            .attr('width', this.width + this.margin.left + this.margin.right)
            .attr('height', this.height + this.margin.top + this.margin.bottom);
        ...
    }
    ...
}

The html is pretty simple:

<div class="d3-chart" #chart></div>

I've uploaded the whole project in this project: https://github.com/Gimly/d3NetCoreAngular2

@MarkPieszak
Copy link
Contributor

Can you try wrapping the logic inside of the ngOnInit with the code below and see if it works? D3 doesn't readily get rendered on the server so you might have to make sure it's only rendered on the client.

import { isBrowser } from 'angular2-universal'; // add this uptop

if (isBrowser) {
   // your d3 code
}

@Gimly
Copy link
Author

Gimly commented Dec 7, 2016

Thanks! It does work indeed!
I fought that issue for nearly a week, and it's fixed in just a line... 👍 Oh well, that's how you learn.

Does it mean that my guess was correct? When I access the URL directly, the code is pre-rendered server-side?

Care to explain what in the template activates that server-side rendering?

@MarkPieszak
Copy link
Contributor

MarkPieszak commented Dec 7, 2016

Well when you refresh, or type an address and press enter you're hitting the server side, which in turn tries to render that specific section of the application with Angular Universal. Once that's rendered to a string and loaded on the page, the client-side bootstrap starts and then takes over, and the rest of the your time navigating is like a normal SPA. But because of that initial server side render, you get performance boosts, real SEO (since all crawlers can reach your site now), and deep-linking (think social media sharing), all at your finger tips.

You just have to always keep in mind how your code / components will be handled on the server(Node) side, and you'll be good to go :)

@SteveSandersonMS
Copy link
Member

Thanks for resolving this @MarkPieszak!

@Gimly
Copy link
Author

Gimly commented Dec 8, 2016

Thanks a lot for your help and explanation @MarkPieszak.

@flaneuse
Copy link

FYI isBrowser() has been switched to isPlatformBrowser. See Angular Universal Gotchas

import { Component, Inject, PLATFORM_ID, OnInit } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements {

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object
  ){
  }

  ngOnInit(){
    if (isPlatformBrowser(this.platformId)) {
     //Client only code.
    } else {
     //Server only code.
    }
  }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants