While Grouping Additional Links are getting created

Hi,
While creating groups if there are more than 2 nodes in groups, then addition self links are getting generated.And while changing orientation, we are changing groups warping column as well at that time also additional links are getting generated,
Can you please suggest were are we going wrong
Below I have pasted the groupTemplate, linkTemplate, changing orientation function:
I have tried many approaches but links are getting created any how.
have also attached grouped and un-grouped images

//Node Template
dia.nodeTemplateMap.add('DataNode', $Go(go.Node, 'Position', {`
	name: "BODY", 
	width: 150, 
	height: 32,
	selectionAdorned: false,
	toolTip:
    $Go(go.Adornment, 'Auto',
          $Go(go.Shape, 'RoundedRectangle', { 
      fill: '#ffffff', 
      strokeWidth: 0.5, 
      stroke: '#C7C7C7', 
      minSize: new go.Size(0, 48) 
      }),
          $Go(go.TextBlock, { 
      margin: 8, 
      wrap: go.TextBlock.WrapFit, 
      font: '12px sans-serif', 
      stroke: '#333333' 
      },
          new go.Binding('text', 'name'))
        )
  },
    $Go(go.Shape, "RoundedRectangle",{  
    name: "SHAPE", 
    fromLinkable: true, 
    toLinkable: true,
    fill: "#F0F0F0", 
    stroke: '#B4B4B4', 
    strokeWidth: 1, 
    maxSize: new go.Size(149, 31), 
    minSize: new go.Size(149, 31) 
  },
  new go.Binding("fill", "isSelected", function(sel) {
    return sel ? '#EDF7FF': '#F0F0F0';
  }).ofObject(""),
  new go.Binding("stroke", "isSelected", function(sel) {
    return sel ? '#7198C9' : '#B4B4B4';
  }).ofObject("")
  ),//End of Main Body
  $Go(go.Panel, 'Horizontal',
        { position: new go.Point(6, 0) },
        $Go(go.Panel, 'Table',
          {
            minSize: new go.Size(150, 32),
            maxSize: new go.Size(150, 32),
            defaultAlignment: go.Spot.Left
          },
          $Go(go.RowColumnDefinition, { column: 0, width: 118 }),
          $Go(go.RowColumnDefinition, { column: 1, width: 22 }),
          $Go(go.TextBlock, {
            font: '10px  Segoe UI,hydux', stroke: '#333333', minSize: new go.Size(123, 11), maxSize: new go.Size(123, 11)
          },
            {
              row: 0, column: 0,
              editable: true, isMultiline: false,
              stretch: go.GraphObject.Horizontal,
              wrap: go.TextBlock.WrapFit,
              overflow: go.TextBlock.OverflowEllipsis
            }, new go.Binding('text', 'name').makeTwoWay()),
          $Go(go.Panel, 'Table',
            {
              minSize: new go.Size(22, 30),
              maxSize: new go.Size(22, 30),
              background: '#FFFFFF',
              row: 0, column: 1,
              defaultAlignment: go.Spot.Center
            },
            new go.Binding("background", "isSelected", function(sel) {
              return sel ? '#CCEBFF' : '#FFFFFF';
            }).ofObject(""),
            $Go(go.RowColumnDefinition, { row: 0, height: 15 }),
            $Go(go.RowColumnDefinition, { row: 1, height: 10 }),
            $Go(go.TextBlock, '10',
              { row: 0, editable: false, isMultiline: false },
              { font: '12px  Segoe UI,sans-serif', stroke: '#5d5d5d', minSize: new go.Size(22, 13), maxSize: new go.Size(22, 13) }
            )
          )  // end Table Panel
        ),
      ) // end Horizontal Panel
)//End of Node
);


//Group Template
// Vertical Group Node
 dia.groupTemplateMap.add('verticalGroup', $Go(go.Group, 'Auto',
{
// memberValidation: handleMemberValidation,
mouseDragEnter: function (e, grp, prev) { highlightGroup(e, grp, true); },
mouseDragLeave: function (e, grp, next) { highlightGroup(e, grp, false); },
// when the selection is dropped into a Group, add the selected Parts into that Group;
// if it fails, cancel the tool, rolling back any changes
mouseDrop: finishDrop,
background: 'transparent',
ungroupable: true,
computesBoundsAfterDrag: true,
handlesDragDropForMembers: true,  // don't need to define handlers on member Nodes 
and Links
// Groups containing Nodes lay out their members vertically
layout:
 $Go(go.GridLayout,
   {
     wrappingColumn: 1, alignment: go.GridLayout.Position,
     cellSize: new go.Size(1, 1), spacing: new go.Size(4, 4)
   })
},
new go.Binding("layout", "vertical", function (isVertical) {
return $Go(go.GridLayout, { wrappingColumn: (isVertical ? 1 : 0) });
}),
new go.Binding('background', 'isHighlighted', function (h) { return h ? 'rgba(255,0,0,0.2)' : 'transparent'; }).ofObject(),
$Go(go.Shape, 'Rectangle',
  { fill: null, stroke: '#4A90E2', strokeWidth: 2 }),
$Go(go.Panel, 'Vertical',  // title above Placeholder
$Go(go.Panel, 'Horizontal',  // button next to TextBlock
 { stretch: go.GraphObject.Horizontal, background: '#4A90E2' },
 $Go('SubGraphExpanderButton',
   { alignment: go.Spot.Right, margin: 5 }),
 $Go(go.TextBlock, 'New Group',
   {
     alignment: go.Spot.Left,
     editable: true,
     margin: 5,
     font: 'bold 16px sans-serif',
     opacity: 0.75,
     stroke: '#FFFFFF'
   },
   new go.Binding('text', 'text').makeTwoWay())
),  // end Horizontal Panel
$Go(go.Placeholder,
 { padding: 5, alignment: go.Spot.TopLeft })
)  // end Vertical Panel
) // end Group and call to add to template Map
);



//Link Template
dia.linkTemplate = $Go(go.Link, {
selectable: false,      // links cannot be selected by the user
// curve: go.Link.Bezier,
 routing: go.Link.AvoidsNodes,
  corner: 15, // curve: go.Link.JumpOver,
 toEndSegmentLength: 20,
layerName: 'Background'  // don't cross in front of any nodes
}, $Go(go.Shape,  // this shape only shows when it isHighlighted
{ isPanelMain: true, stroke: null, strokeWidth: 5 },
new go.Binding('stroke', 'isHighlighted', function (h) { return h ? 'red' : null; }).ofObject()
),
$Go(go.Shape,
// mark each Shape to get the link geometry with isPanelMain: true
{ isPanelMain: true, stroke: '#979797', strokeWidth: 1 },
new go.Binding('stroke', 'color')),
$Go(go.Shape,   // the arrowhead
{ toArrow: 'Triangle', scale: 0.7, fill: '#979797', stroke: '#979797' })
);

//Flip View 
public flipView() {
// @ts-ignore
this._flipToggle = !this._flipToggle;
this.goJsElement.diagram.commandHandler.archetypeGroupData.vertical  ==  ( this._angle == 0);
this._angle = this._angle == 0 ? 90 : 0;
this.goJsElement.diagram.commit(function(diag) {
diag.findTopLevelGroups().each(function(g) {
diag.model.set(g.data, "vertical", !g.data.vertical);
});
}, "toggled layout direction");
this.goJsElement.diagram.startTransaction( "change Layout" );//This adds animation for transmision
this.goJsElement.diagram.layout.angle = this._angle;  //value can be  0(left to right)/90(top to bottom)/180(right to left)/270(bottom to top)
this.goJsElement.diagram.commitTransaction("change Layout");
this.goJsElement.diagram.undoManager.clear();
}
var highlightGroup = function (e, grp, show) {
if (!grp) return;
e.handled = true;
if (show) {
// cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
// instead depend on the DraggingTool.draggedParts or .copiedParts
var tool = grp.diagram.toolManager.draggingTool;
var map = tool.draggedParts || tool.copiedParts;  // this is a Map
// now we can check to see if the Group will accept membership of the dragged Parts
if (grp.canAddMembers(map.toKeySet())) {
grp.isHighlighted = true;
return;
}
}
grp.isHighlighted = false;
}
//Add/Remove Node to/from group on drag-drop 
var finishDrop = function (e, grp) {
var ok  = (grp !== null ? grp.addMembers(grp.diagram.selection, true): e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
if (!ok) e.diagram.currentTool.doCancel();
}

It appears to be some problem that happens when you flip. You will need to debug what you do then. Does it ever happen when the user makes other changes?

Not only flip if you just observe the image where first time group is created at that time itself a extra link is generated.

Untitled

In-fact While Debugging it i got to know that not only Grouped node’s links, but all the links are getting duplicated.
And After just grouping it if I do undo then the duplicate links are removed, and what ever operations i am doing further are working fine

Also on checking undoManager other than Group there is one more operation:
"update data"

What do you do for “update data”?
How do you implement grouping?

We not doing anything in update data.
For Grouping we are using following command while grouping it through button:
this.goJsElement.diagram.commandHandler.groupSelection();
else grouping it by shortcut by pressing “Ctrl+G”.

Can you reproduce the problem in samples such as Basic GoJS Sample ?
Please tell me how to reproduce the problem.

I think you could reproduce it with your angular code.

I have done demo without angular first and now shifted to angular.
In my non-angular demo it is working absolutely fine I think it is giving issue in Angular verion.

Else if you want i can share my all code.
Let me know what will you prefer.
Thank you in advance

While debugging,
I got to know this was happening due to the following code :

//When the diagram model changes, update app data to reflect those changes
public diagramModelChange =  function (changes: go.IncrementalData) {
    this.diagramNodeData = DataSyncService.syncNodeData(changes, this.diagramNodeData);
    this.diagramLinkData = DataSyncService.syncLinkData(changes, this.diagramLinkData);
    this.diagramModelData = DataSyncService.syncModelData(changes, this.diagramModelData);
}

This Code I have got with your basic Angular Template.
Removing this it is working fine, but while removing this will it have impact on my data sync…?
If so Please guide me to resolve this issue?

Could you describe what your data management policies are? How do you determine identity? What objects may be shared? May data be mutated? Please read GoJS Using Models -- Northwoods Software for more discussion.

Hello,

I am the one who made the GoJS/Angular Components. I tried reproducing your group-related link-duplicating issue with the Angular Basic sample this morning, but cannot.

I’d be very curious to see if I can find the issue. Can you upload your full Angular project for me to look at?

Hi Ryanj,

Please find my code below for your reference:

import * as go from 'gojs';
import { DataSyncService, DiagramComponent } from 'gojs-angular';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-go-js',
  templateUrl: './go-js.component.html',
  styleUrls: ['./go-js.component.less'],
  encapsulation: ViewEncapsulation.None
})

export class GoJsComponent implements OnInit{
    constructor(){}
    ngOnInit(): void{}
    
    public diagramNodeData: Array<go.ObjectData> =  [{ "key": "-1", "name": "Start", "category": "Circle" }, { "key": 0, "name": "Node 2", "category": "DataNode" }, { "key": 13, "name": "Node 3", "category": "DataNode" }, { "key": 14, "name": "Node 4", "category": "DataNode" }, { "key": 21, "name": "Node 5", "category": "DataNode" }, { "key": "-2_0", "name": "End", "category": "Circle" }, { "key": 20, "name": "Node 7", "category": "DataNode" }, { "key": 3, "name": "Node 8", "category": "DataNode" }, { "key": 4, "name": "Node 9", "category": "DataNode" }, { "key": 32, "name": "Node 10", "category": "DataNode" }, { "key": 33, "name": "Node 11", "category": "DataNode" }, { "key": 34, "name": "Node 12", "category": "DataNode" }, { "key": 35, "name": "Node 13", "category": "DataNode" }, { "key": 36, "name": "Node 14", "category": "DataNode" }, { "key": "-2_1", "name": "End", "category": "Circle" }, { "key": 1, "name": "Node 16", "category": "DataNode" }, { "key": 2, "name": "Node 17", "category": "DataNode" }, { "key": 5, "name": "Node 18", "category": "DataNode" }, { "key": "-2_2", "name": "End", "category": "Circle" }, { "key": 6, "name": "Node 20", "category": "DataNode" }, { "key": 7, "name": "Node 21", "category": "DataNode" }, { "key": 8, "name": "Node 22", "category": "DataNode" }, { "key": 9, "name": "Node 23", "category": "DataNode" }, { "key": 10, "name": "Node 24", "category": "DataNode" }, { "key": "-2_3", "name": "End", "category": "Circle" }, { "key": 11, "name": "Node 26", "category": "DataNode" }, { "key": "-2_4", "name": "End", "category": "Circle" }, { "key": 30, "name": "Node 28", "category": "DataNode" }, { "key": 31, "name": "Node 29", "category": "DataNode" }, { "key": "-2_5", "name": "End", "category": "Circle" }, { "key": 12, "name": "Node 31", "category": "DataNode" }, { "key": "-2_6", "name": "End", "category": "Circle" }, { "key": "-2_7", "name": "End", "category": "Circle" }, { "key": "d_-1", "category": "Decision", "name": "Node 34" }, { "key": "d_0", "category": "Decision", "name": "Node 35" }, { "key": "d_4", "category": "Decision", "name": "Node 36" }, { "key": "d_11", "category": "Decision", "name": "Node 37" }, { "key": "d_14", "category": "Decision", "name": "Node 38" }, { "key": "d_20", "category": "Decision", "name": "Node 39" }, { "key": "d_34", "category": "Decision", "name": "Node 40" }];
    public diagramLinkData: Array<go.ObjectData> = [{ "from": -1, "to": "d_-1" }, { "from": "d_-1", "to": "0" }, { "from": "d_-1", "to": "12" }, { "from": "d_-1", "to": "30" }, { "from": "d_-1", "to": "11" }, { "from": "d_-1", "to": "20" }, { "from": "d_-1", "to": "6" }, { "from": 0, "to": "d_0" }, { "from": "d_0", "to": "-2_7" }, { "from": "d_0", "to": "13" }, { "from": 1, "to": "2" }, { "from": 2, "to": "3" }, { "from": 3, "to": "4" }, { "from": 4, "to": "d_4" }, { "from": "d_4", "to": "5" }, { "from": "d_4", "to": "32" }, { "from": 5, "to": "-2_2" }, { "from": 6, "to": "7" }, { "from": 7, "to": "8" }, { "from": 8, "to": "9" }, { "from": 9, "to": "10" }, { "from": 10, "to": "-2_3" }, { "from": 11, "to": "d_11" }, { "from": "d_11", "to": "-2_4" }, { "from": "d_11", "to": "11" }, { "from": 12, "to": "-2_6" }, { "from": 13, "to": "14" }, { "from": 14, "to": "d_14" }, { "from": "d_14", "to": "3" }, { "from": "d_14", "to": "1" }, { "from": "d_14", "to": "21" }, { "from": 20, "to": "d_20" }, { "from": "d_20", "to": "0" }, { "from": "d_20", "to": "13" }, { "from": 21, "to": "-2_0" }, { "from": 30, "to": "31" }, { "from": 31, "to": "-2_5" }, { "from": 32, "to": "33" }, { "from": 33, "to": "34" }, { "from": 34, "to": "d_34" }, { "from": "d_34", "to": "34" }, { "from": "d_34", "to": "35" }, { "from": 35, "to": "36" }, { "from": 36, "to": "-2_1" }];
    public diagramDivClassName: string = 'myDiagramDiv';
    public diagramModelData = { prop: 'value' };
    // When the diagram model changes, update app data to reflect those changes
    public diagramModelChange = function(changes: go.IncrementalData) {
        this.diagramNodeData = DataSyncService.syncNodeData(changes, this.diagramNodeData);
        this.diagramLinkData = DataSyncService.syncLinkData(changes, this.diagramLinkData);
        this.diagramModelData = DataSyncService.syncModelData(changes, this.diagramModelData);
    };
    public initDiagram(): go.Diagram{
        const $Go = go.GraphObject.make;
// this function is used to highlight a Group that the selection may be dropped into
var highlightGroup = function (e, grp, show) {
  if (!grp) return;
  e.handled = true;

  if (show) {
    // cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
    // instead depend on the DraggingTool.draggedParts or .copiedParts
    var tool = grp.diagram.toolManager.draggingTool;
    var map = tool.draggedParts || tool.copiedParts;  // this is a Map
    // now we can check to see if the Group will accept membership of the dragged Parts
    if (grp.canAddMembers(map.toKeySet())) {
      grp.isHighlighted = true;
      return;
    }
  }
  grp.isHighlighted = false;
}

//Add/Remove Node to/from group on drag-drop 
var finishDrop = function (e, grp) {
  var ok = (grp !== null ? grp.addMembers(grp.diagram.selection, true) : e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
  if (!ok) e.diagram.currentTool.doCancel();
}

const dia = $Go(go.Diagram, {
  'undoManager.isEnabled': true,
  'commandHandler.archetypeGroupData': { isGroup: true },
  'dragSelectingTool.isEnabled': true,
  'SelectionDeleting': function (e) {
    e.subject.each(function (node) {
      if (node.category != "DataNode") { return; }
      node.findNodesInto().each(function (from) {
        node.findNodesOutOf().each(function (to) {
          // don't create duplicate links??
          if (from.findLinksTo(to).count > 0) { return; }
          let model = to.diagram.model;
          // NOTE: simplify this code depending on which kind of model you are using
          if (model instanceof go.GraphLinksModel) {
            let newlinkdata = {};  // add whatever properties you need
            model.setFromKeyForLinkData(newlinkdata, from.key);
            model.setToKeyForLinkData(newlinkdata, to.key);
            model.addLinkData(newlinkdata);
          } else if (model instanceof go.TreeModel) {
            model.setParentKeyForNodeData(to.data, from.key);
          }
        });
      });
    });
  },
  layout: $Go(go.TreeLayout, {
    'layerSpacing': 40, 
    'nodeSpacing': 20, 
    'angle': 90	
  }
  )
});
//Start/End Node
dia.nodeTemplateMap.add('Circle',
  $Go(go.Node, 'Auto',
    $Go(go.Shape, 'Circle', {
      fill: '#FFFFFF',
      strokeWidth: 1,
      stroke: 'gray',
      width: 50,
      height: 50
    }),
    $Go(go.TextBlock, {
      stroke: 'gray',
      font: '11px sans-serif'
    },
      new go.Binding('text', 'name'))
  )
);
//Data Node
dia.nodeTemplateMap.add('DataNode', 
$Go(go.Node, 'Auto',
  $Go(go.Shape, 'RoundedRectangle', {
    fill: '#FFFFFF',
    strokeWidth: 1,
    stroke: 'gray',
    width: 150,
    height: 30
  }),
  $Go(go.TextBlock, {
    stroke: 'gray',
    font: '11px sans-serif'
  },
    new go.Binding('text', 'name'))
)
 ); 
// Decision Node
dia.nodeTemplateMap.add('Decision',
  $Go(go.Node, 'Spot',
    $Go(go.Panel, 'Auto',
      { width: 50, height: 50 },
      $Go(go.Shape, 'Diamond', { fill:"white", stroke: 'gray' }),
    )
  )
);

// Link Template
dia.linkTemplate = $Go(go.Link, {
  routing: go.Link.AvoidsNodes,
  corner: 15, 
},
  $Go(go.Shape),
  $Go(go.Shape,  
    { toArrow: 'Triangle'})
);
return dia;
    }
}

Let me know if you need anything else.

You have not defined a linkKeyProperty for your model, so when the DataSyncService tries to merge changes in the diagram’s links with changes in the app’s links data, it has no way to identify what links have been changed.

You can read more about this here: GoJS and Angular -- Northwoods Software

To fix this, set the linkKeyProperty of your Diagram’s model to something like “key”, then make sure each data object for your links has a unique value for its “key” property. Observe the GoJS/Angular Basic sample to see how this is done.

Thank you Ryanj…
Its working after defining linkKeyProperty…