Typescript/D3 v4 - Context of this in d3.drag().on(“end”, this.dragended)

痴心易碎 提交于 2019-12-01 17:31:37

问题


I am using the D3 library to move an item within a Venn-diagram. When I stop dragging I want to determine the position of the item in the diagram.

item.call(d3.drag()
    .on("start", this.dragstarted)
    .on("drag", this.dragged)
    .on("end", this.dragended)
);

These are the functions I call when the dragging starts, is going on and ends.

dragended(d: TCMemberScenario, i: number) {
    d3.select(this).classed("active", false);
    d.calculateRoles();
    this.save();
}

This is the function that is called when the dragging has ended. I update some things in the diagram and then I want to call the save method. This is another method in the class. However, the this variable is referencing to the D3 object and not the instance of the class. So I get a "Uncaught TypeError: Cannot read property 'save' of undefined"

How can I call from the dragended method another method of my class?


回答1:


To keep reference to 'this' use arrow functions like this:

item.call(d3.drag()
    .on("start", (d, i) => this.dragstarted(d,i))
    .on("drag", (d, i) => this.dragged(d, i))
    .on("end", (d, i) => this.dragended(d, i))
);



回答2:


If you need to keep the reference to the class instance and also the element instance referenced by d3 drag you can define your listener functions as:

export class MyClass {
    @Input()
    radius: number = 45;

    constructor() {
        d3.drag()
          .on("start", this.dragStarted(this))
          .on("drag", this.dragged(this))
          .on("end", this.dragEnded(this));
    }

    private dragged(self) {
        return function(d) {
            // 'this' in this context will be the d3 element

            d3.select(this)
              .attr("cx", d.x = self.radius * Math.cos(alpha))
              .attr("cy", d.y = d3.event.y < 0 ? -self.radius * Math.sin(alpha) : self.radius * Math.sin(alpha));
        }
    }

    ...

}

Tested with d3.js v4




回答3:


As a matter of principle, D3 binds the this context of callbacks on selections, transitions and the like to the DOM element on which they operate.

By implication, if you need to access the this context of a "wrapping object" in a lexical scope sense, you cannot use this to access it. Applied to your specific case:

  • d3.select(this) will operate on the currently iterated DOM element which will have the type of the DOM element underlying the item selection. So, if your item is e.g. of type SVGCircleElement, such will be the this context of your dragended function.
  • A way to accessing the wrapping objects this context as well is by creating a closure around the dragended function:

You can add a private method to your wrapping object which has the save method:

private getDragEndedHandler() {
  let self = this; // object context to preserve
  return function(d: TCMemberScenario, i: number) {
    d3.select(this).classed("active", false); // D3 bound DOM element context
    d.calculateRoles();
    self.save(); // closure-preserved object context
  }
}

Now, when you bind the handler event, you simply do the following:

item.call(d3.drag()
    .on("start", this.dragstarted)
    .on("drag", this.dragged)
    .on("end", this.getDragEndedHandler())
);

The same pattern can be applied to the other event handlers if need be.

If you use the latest definitions for d3-selection and d3-drag from npm @types, you will see that they now have generics to be more explicit about the this context typing pertaining to DOM elements and D3 callbacks.



来源:https://stackoverflow.com/questions/38945311/typescript-d3-v4-context-of-this-in-d3-drag-onend-this-dragended

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!