When I change values of a specific node it changes values for all instances of that node

Hello,
I am trying to make a POC based on GOJS in Angular. When I change the data of a node it applies to every instance of that node in the diagram instead of that specific node.

whenever a new node is selected in the diagram a listener is activated

   this.diagram.addDiagramListener("ChangedSelection", e => {
      const node = e.diagram.selection.first();
      this.nodeSelected.emit(node instanceof go.Node ? node : null);
    });

this listener copies the selected node to a variable “node” in another component which builds an inspector for the nod.data by copying over all “data” into a second object called data where it is edited.

once your ready to save that data is pushed back to the node using the following

  onCommitDetails() {
    if (this.node) {

      // copy the edited properties back into the node's model data,
      this.model.startTransaction();
      this.model.setDataProperty(this.node.data, "properties", this.data.properties);
      this.model.commitTransaction("modified properties");
      console.log(this.model.toJson());
    }
  }

or in the case of adding ports:

  addInputPort(name) {
    if(this.node) {
    // this.data.outArray.push({ portId: "test"});
    this.model.startTransaction("addPort");
      // get the Array of port data to be modified
      if (this.node.data["properties"]["Options"]) {
        // create a new port data object
        console.log(this.node.data["properties"]["Options"])
        const newportdata = {
          portId: name
        };
        // and add it to the Array of port data

        this.model.insertArrayItem(this.node.data["properties"]["Options"], -1, newportdata);
      }
    this.model.commitTransaction("addPort");
    }
  }

What happens is every node in the diagram that is of the same type copied from the palette ends up being updated with the changes not just one the selected
before:

after:

I need each one to be unique and not share data.

This is an open source project so you can run it yourself: https://github.com/quasarke/ArkeManager
I have tried so far the following:

  • making sure keys are unique and all palette and initial graphlinksmodel items have a key property

  • adding a uniquekeygenerator

Your node data objects appear to have properties whose values are Objects or Arrays, yes? In fact the property name appears to be “properties”, but there also appear to be sub-sub-properties on “Options”.

Whenever you create a model, do you make sure that those values are copied, not shared? You probably need to specify a custom Model | GoJS API.

hey walter thanks for the reply! I think your on the money!

The node is generated buy a method called makeTemplate() this method gets passed a “name”,“color”, an array of ports for the top, array of ports for the bottom, the “properties” that you pointed out, and a description for the tool tip. an example call looks like this.

this.makeTemplate(
      "GetInput"    ,
  "lightblue",
  ["IN"],
  [
    "NextStep",
    "NoAction",
    "Invalid"
  ],
  {
    MaxDigitTimeoutInSeconds: 1,
    Direction: "",
    NumberOfDigitsToWaitForNextStep: 1,
    TerminationDigit: "",
    SetValueAsDestination: "",
    Options: [{ portId: "1" }]
  },
  "Gets input from the phone through DTMF"
);`

here is the function:

 makeTemplate(
    typename,
    background,
    inports,
    outports,
    properties,
    description?
  ) {
    const node: go.Node = this.$(
      go.Node,
      "Auto",
      this.$(
        go.Shape,
        "RoundedRectangle",
        { fill: "white", strokeWidth: 1, minSize: new go.Size(150, 100) },
        new go.Binding("fill", "color")
      ),
      this.$(
        go.TextBlock,
        { margin: 8, editable: false },
        new go.Binding("text").makeTwoWay()
      ),

      this.$(
        go.Panel,
        "Horizontal",
        {
          alignment: go.Spot.Top,
          alignmentFocus: new go.Spot(0.5, 0, 0, -8)
        },new go.Binding("itemArray", "inArray"),{
          itemTemplate: this.makePort(true)
          }
      ),
      this.$(
        go.Panel,
        "Horizontal",
        new go.Binding("itemArray", "outArray"),
        {
          alignment: go.Spot.Bottom,
          alignmentFocus: new go.Spot(0.5, 1, 0, 8),
        },{
        itemTemplate: this.makePort(false)
        }
      ),

      {
        // define a tooltip for each node that displays the color as text
        toolTip: this.$(
          go.Adornment,
          "Auto",
          this.$(go.Shape, { fill: "#FFFFCC" }),
          this.$(go.TextBlock, { margin: 4 }, description)
        )
        // end of Adornment
      }
    );
    if (properties.Options) {
      node.add(this.makeInputPort())
    }
    this.diagram.nodeTemplateMap.add(typename, node);
    let inArray: Array<Object> = inports.map(x => ({ portId: x }));
    let outArray: Array<Object> = outports.map(x => ({ portId: x }));
    this.paletteTest.push({
      key: "",
      text: typename,
      color: background,
      category: typename,
      properties: properties,
      description: description,
      inArray: inArray,
      outArray: outArray
    });
  }

when my model is created I do the following

    this.model.copyNodeDataFunction = function(data, model) {
      let newdata: any = Object.assign({}, data);
      let i = model.nodeDataArray.length * 2 + 1;
      while (model.findNodeDataForKey(i) !== null) i += 2;
      newdata.key = i;
      console.log(newdata);
      return newdata;

    } 

and it seems to not be working I can see each one creates a unique key: “” but not unique port __GoHashID when its copied from the palette

first GetInput:

second GetInput:

is there something more I need to do to make sure all of this is unique? I should say I copied this from a post here when I discovered the issue.

You don’t need to worry about unique identifiers or any __gohashid property – in fact you are explicitly not supposed to copy such internal properties, but only the properties that you care about.

And you need to do so by doing a deeper copy than the standard shallow copy that you are doing by calling Object.assign.

Ok so deep copy was the key. I used this stack overflow suggestion (though this seems ineffecient) and it works. I am assuming by converting it to a string and then back to an object. it ensures its a deep copy. heres the updated copeNodeDataFunction.

this.model.copyNodeDataFunction = function(data, model) {
  let newdata: any = JSON.parse(JSON.stringify(data));
  let i = model.nodeDataArray.length * 2 + 1;
  while (model.findNodeDataForKey(i) !== null) i += 2;
  newdata.key = i;
  console.log(newdata);
  return newdata;

}

Thank you so much for your help walter I was really stuck there for a bit.

Don’t set the key that way unless you really need to. That was just an example of how to get keys that were always odd values.

In fact, don’t set the key at all. That’s the responsibility of Model | GoJS API, whose behavior can be changed by specifying Model | GoJS API.

Ok I was going to change it out to a GUID generator but i don’t really need to

this.model.copyNodeDataFunction = function(data, model) {
  let newdata: any = JSON.parse(JSON.stringify(data));
  return newdata;
}

I’ve removed the key generation parts from the app. Thanks!