An error occurs at runtime in Angular 2/Typescript when trying to run a function that is undefined

I am currently developing an Angular component (specifically with Angular2/Angular4, not AngularJS) to generate a D3.js Navigation bar. While I haven't experienced any problems with other D3 charts, I encounter a runtime error when attempting to access one of the class variables during the execution of a "brush." This issue seems to be related to Angular/Typescript rather than D3: 'undefined is not a function' occurs when trying to access "this.x" in the "brushed()" function within the code snippet below.

Could someone provide insight on what steps need to be taken to access "this.x" and "this.x.invert" in the "brushed()" function?

import { Component, OnInit, OnChanges, ViewChild, ElementRef, Input, ViewEncapsulation } from '@angular/core';
import * as d3 from 'd3';
import {StockData} from "../../dataServices/stockData";

@Component({
  selector: 'app-navchart',
  templateUrl: './navchart.component.html',
  styleUrls: ['./navchart.component.css'],
  encapsulation: ViewEncapsulation.None
})
export class NavchartComponent implements OnInit, OnChanges {
  @ViewChild('chart') public chartContainer: ElementRef;
  @Input() public stockdata: StockData;

  public margin: any = { top: 20, bottom: 20, left: 20, right: 20};
  public width : number;
  public height: number;
  public svg: any;
  public g: any;
  public chart: any;
  public x: any;
  public y: any;
  public navline: any;
  public navarea: any;
  public data: any;
  public brush: any;


  constructor() { }

  ngOnInit() {
    console.log("Inside the charting - updating the data");
    if (this.stockdata) {
      //console.log(JSON.stringify(this.stockdata));

      this.data = this.stockdata.stocklist;

      setTimeout(() => {
        this.initChart();
        this.drawAxis();
        this.drawRange();
      }, 500);
    }
  }

  ngOnChanges() {

  }

  public initChart(): void {
    let element = this.chartContainer.nativeElement;
    this.width = element.offsetWidth - this.margin.left - this.margin.right;
    this.height = element.offsetHeight - this.margin.top - this.margin.bottom;

    this.svg = d3.select(element).append('svg')
      .attr('width', element.offsetWidth)
      .attr('height', element.offsetHeight);

    this.g = this.svg.append('g')
      .attr("transform", "translate(" + this.margin.left + "," + this.margin.top +")");


    // x and y scale functions - called every time a value needs converted to pixel location
    //this will need moved to a "redraw" function when adjustments to overall chart size are allowed
    this.x = d3.scaleTime()
            .range([0, this.width]);

    this.x.domain(d3.extent(this.data, (d: any) => new Date(d.date )));

    this.y = d3.scaleLinear()
      .range([this.height, 0]);


      //sets the limits of x and y data.
      // this will need to be moved to a redraw when changes to dataset ranges are allowed
      this.y.domain([
        d3.min(this.data, (d: any) => d.close),
        d3.max(this.data, (d: any) => d.close)
      ]);
      console.log ("Min = " + d3.min(this.data, (d: any) => d.close) );

      // line drawing functions
      this.navline = d3.line()
        .curve(d3.curveBasis)
        .x( (d: any) => this.x(new Date(d.date)) )
        .y( (d: any) => this.y(d.close) );

      this.navarea = d3.area()
        .curve(d3.curveBasis)
        .x( (d: any) => this.x(new Date(d.date)) )
        .y1( (d: any) => this.y(d.close) )
        .y0( (d: any) => this.height );



      this.g.append("g")
        .attr("class", "brush")
        .call(d3.brushX().on("end",  this.brushed));
  }


 /* Error is in this function.  It cannot find "this.x" from the class,
  * and gives an undefined error.
  *  Right now the function is just setting debug content, but when 
  * this.x is working, I will add .invert() to it to get the original
  * date values associated with the pixel location on the x-axis.
  */
  public brushed(): void {
    console.log(JSON.stringify(d3.event.selection));
    //returns proper [x0,x1] pixel values such as [102,500] on a svg 800 pixels wide.

    let dat: any = d3.event.selection.map( this.x);
    //let dat: any = d3.event.selection.map( this.x.invert) also fails with "invert does not exist on undefined"
    console.log(JSON.stringify(dat));

    //The error appears to be because it can't find this.x, even though that is declared and works in
    // other sections of the same class.
  }

  //draw x and y axes
  public drawAxis(): void {
      this.g.append("g")
        .attr("class", "axis axis--x")
        .attr("transform", "translate(0," + this.height + ")")
        .call(d3.axisBottom(this.x));

    }

    public drawRange(): void {

      this.g.append("path")
        .attr("class", "area")
        .attr("d",  this.navarea(this.data) );


      this.g.append("path")
        .attr("class", "line")
        .attr("d",  this.navline(this.data) );
    }


}

If it is important, the data is simply an array of daily stock entries in the format:

[ {date, open, high, low, close} ...] {"date":"2017-06-07 13:02:00","open":"72.6350","high":"72.7700","low":"71.9500","close":"72.0800","volume":"9247460","adjClose":"72.6350"}

D3 likes to use the "d" reference

Answer №1

Please take note: This particular question seems to be a duplicate of another one. While I would typically mark it as such, the linked question lacks an explanation, so I'll try to provide some insights here.


When you pass a callback function to .call, d3 changes what the variable this points to within that callback. Instead of your class, it now references the g node from the selection.

You have two options to address this issue. First, encapsulate it in an ES6 fat arrow function:

.call( d3.brushX().on("end", () => this.brushed() ) );    

This approach creates a closure around this and maintains the reference to your class.

Alternatively, you can use .bind to ensure the preservation of this. .bind enforces the retention of the proper context.

.call( d3.brushX().on("end", this.brushed.bind(this) ) ); 

For further insight, check out this valuable resource on this topic.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using Typescript to import an npm package that lacks a definition file

I am facing an issue with an npm package (@salesforce/canvas-js-sdk) as it doesn't come with a Typescript definition file. Since I am using React, I have been using the "import from" syntax to bring in dependencies. Visual Studio is not happy about th ...

Is there a way to collapse just one specific row in Angular?

I am struggling to toggle only the selected row, any suggestions? Take a look at my code and demonstration here: https://stackblitz.com/edit/test-trainin-2-gv9glh?file=src%2Fapp%2Fapp.component.scss Currently, all rows are being toggled when clicked, but ...

Angular 4 incorporates ES2017 features such as string.prototype.padStart to enhance functionality

I am currently working with Angular 4 and developing a string pipe to add zeros for padding. However, both Angular and VS Code are displaying errors stating that the prototype "padStart" does not exist. What steps can I take to enable this support in m ...

Encountering problems with displaying the index value in *ngFor directive in Angular 5

I am encountering a problem with rendering the index of *ngFor directive for a specific scenario as described below. Suppose we have an array of objects like this: this.temp = [ {name:'John',age:24,visibility:'visible'}, {name:&ap ...

When adjusting the month/year, the Material Date Picker extends beyond its container

Currently, I have an Angular 18 application with a page that includes a material date picker component. When I open the Date Picker, everything displays correctly. However, when I try to change the month using the left/right arrow or the year, the data co ...

`Angular application utilizing NgRX for state management`

Is it necessary to implement ngRx for state management in an Angular application? I have noticed that some companies adopt it, while others do not. Personally, I have over 2 years of experience developing Angular applications and I haven't encountered ...

Using Typescript to define custom PopperComponent props in Material UI

I'm currently utilizing the Material UI autocomplete feature in my React and Typescript application. I'm looking to create a custom popper component to ensure that the popper is full-width. Here's how I can achieve this: const CustomPopper ...

Displaying real-time data from a JSON object in Angular

I am currently working on displaying a JSON object in Angular to the client's HTML. To achieve this, I have implemented the following route on the server side: const express = require('express'); const jsonRoute = express.Router(); jsonRou ...

Update the useState function individually for every object within an array

After clicking the MultipleComponent button, all logs in the function return null. However, when clicked a second time, it returns the previous values. Is there a way to retrieve the current status in each log within the map function? Concerning the useEf ...

Encountered a bug in the findUnique function within the services of a Nest JS and Prisma project

I have a question about using Prisma with Nest. I keep encountering this error: src/modules/auth/auth.service.ts:28:63 - error TS2322: Type 'UserWhereUniqueInput' is not assignable to type 'string'. 28 const user = await this.prisma ...

A guide to setting up Jest for testing a TypeScript/ExpressJS application with typeRoots enabled in the tsconfig.json file

Hey there, I'm currently working on a project which includes TypeScript and Express.js. Right now, my main focus is on setting up the tests. However, when I try to run yarn test (which essentially runs jest without any additional flags), I encounter t ...

"Utilize d3.js to selectively filter through a nested array of objects based on specific object

I have collected a dataset of meteorite landings and organized the data into four keys based on type: var dataByType = d3.nest() .key(function(d) { return d.rectype; }) .entries(dataset); // original dataset You can see the result ...

Utilizing Angular 2's *ngFor to conditionally wrap elements can be compared to organizing a layout with three columns in a Bootstrap row, then starting a

Currently I am facing a challenge with using *ngFor and it has me puzzled. My goal is to incorporate UIkit, but the same concept would apply to Bootstrap as well. <div *ngFor="let device of devices" > <div class="uk-child-width-expand@s uk-te ...

Do we really need to use redux reducer cases?

Is it really necessary to have reducers in every case, or can actions and effects (ngrx) handle everything instead? For instance, I only have a load and load-success action in my code. I use the 'load' action just for displaying a loading spinne ...

Navigating a Laravel application with Angular 7: A comprehensive guide

Setting up a local server with LAMP, I am incorporating Laravel for the backend and Angular 7 for the frontend. Included in my web.php file is: <?php /* |-------------------------------------------------------------------------- | Web Routes |------ ...

The fuse box was to blame for triggering a GET request to http://localhost:4444/, resulting in the error message

I encountered an issue with fuse-box and P5.js where I received a GET http://localhost:4444/ net::ERR_CONNECTION_REFUSED. The complete code can be accessed on GitHub. The fuse.js file contains the following configuration: const { FuseBox, WebIndexPlugin ...

What is the best way to implement Infinite scroll alongside Virtual scroll in Ionic 3?

Having recently delved into the world of Ionic and Angular, I am encountering some difficulties with implementing Infinite scroll alongside Virtual scroll. Despite pushing data such as images, text, and click functions from TypeScript, only empty Ionic car ...

Developing a Next.js application using Typescript can become problematic when attempting to build an asynchronous homepage that fetches query string values

Having recently started delving into the world of React, Next.js, and Typescript, I must apologize in advance if my terminology is not entirely accurate... My current learning project involves creating an app to track when songs are performed. Within the ...

Troubleshooting: Node JS refuses to execute TypeScript file

Running the command node hello-world.ts in Node executes the following code without errors: var f = () => { console.log('Hello World!'); }; f(); However, encountering an issue when attempting to run the file: interface Accountable { ...

Utilizing movingMarker from leaflet-moving-marker in Angular: A Step-by-Step Guide

I am currently working on incorporating the leaflet-moving-marker plugin but encountering some errors in the process. import {movingMarker} from 'leaflet-moving-marker' var myMovingMarker = L.movingMarker([[48.8567, 2.3508],[50.45, 30.523 ...