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?
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))
);
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
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 theitem
selection. So, if youritem
is e.g. of typeSVGCircleElement
, such will be thethis
context of yourdragended
function.- A way to accessing the wrapping objects
this
context as well is by creating a closure around thedragended
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