Angular diagram does not render everytime

Most of time diagram render but once a while when i restart without any code changes, the diagram does not render.
the code below is similar to sample code except I am using TreeViewModel and fetching nodes and links data from API. I believe i need to trigger to diagram once data is fetched and not sure if once data is fetched i need to call this.mydiagramodel.modechanges.emit() or is there a different way to notify diagram that the data has been fetched so it should render. Also, I dont see ngAfterViewInit getting triggered.

Your help is appreciated.

import { ChangeDetectorRef, Component, OnInit, OnChanges, ViewChild, ViewEncapsulation, AfterViewInit } from ‘@angular/core’;

import { ActivatedRoute } from ‘@angular/router’;

import * as go from ‘gojs’;

import { DataSyncService, DiagramComponent, PaletteComponent } from ‘gojs-angular’;

import * as _ from ‘lodash’;

import { forkJoin } from ‘rxjs’;

import { IVRNode } from ‘src/app/models/Node’;

import { IVRLink } from ‘src/app/models/NodeLink’;

import { NodeService } from ‘…/…/services/node.service’;

@Component({

selector: ‘app-viewchart’,

templateUrl: ‘./viewchart.component.html’,

styleUrls: [’./viewchart.component.scss’],

encapsulation: ViewEncapsulation.ShadowDom

})

export class ViewChartComponent implements OnInit, AfterViewInit{

@ViewChild(‘myDiagram’, { static: true }) public myDiagramComponent: DiagramComponent;

@ViewChild(‘myPalette’, { static: true }) public myPaletteComponent: PaletteComponent;

accountid: string;

public diagramNodeData: Array<go.ObjectData>;

public diagramLinkData: Array<go.ObjectData> ;

public diagramDivClassName: string = ‘myDiagramDiv’;

public diagramModelData = { prop: ‘value’ };

public skipsDiagramUpdate = false;

// When the diagram model changes, update app data to reflect those changes

public diagramModelChange = function(changes: go.IncrementalData) {

// when setting state here, be sure to set skipsDiagramUpdate: true since GoJS already has this update

// (since this is a GoJS model changed listener event function)

// this way, we don't log an unneeded transaction in the Diagram's undoManager history

this.skipsDiagramUpdate = true;

this.diagramNodeData = DataSyncService.syncNodeData(changes, this.diagramNodeData);

this.diagramLinkData = DataSyncService.syncLinkData(changes, this.diagramLinkData);

this.diagramModelData = DataSyncService.syncModelData(changes, this.diagramModelData);

};

constructor(private cdr: ChangeDetectorRef, private nodeservice: NodeService, private route: ActivatedRoute) {

this.route.paramMap.subscribe(

  params => {

  console.log(params.get('id'));

  this.accountid = params.get('id');

  }

  );

}

ngOnInit(): void {

let nodedata = this.nodeservice.getnodes(this.accountid);

let linksdata =  this.nodeservice.getlinks(this.accountid);

forkJoin([nodedata, linksdata]).subscribe(results => {

  this.diagramNodeData = results[0];

  this.diagramLinkData = results[1];

  console.log(this.diagramNodeData);

  console.log(this.diagramLinkData);

  this.myDiagramComponent.modelChange.emit();



});

console.log('ngonit ends');

}

ngOnChanges(): void

{

console.log('ngonchanges');

}

ngAfterViewInit(): void {

console.log("after view init");



if (this.observedDiagram) return;

this.observedDiagram = this.myDiagramComponent.diagram;

this.cdr.detectChanges(); // IMPORTANT: without this, Angular will throw ExpressionChangedAfterItHasBeenCheckedError (dev mode only)

const appComp: ViewChartComponent = this;

// listener for inspector

this.myDiagramComponent.diagram.addDiagramListener('ChangedSelection', function(e) {

  if (e.diagram.selection.count === 0) {

    appComp.selectedNode = null;

  }

  const node = e.diagram.selection.first();

  if (node instanceof go.Node) {

    appComp.selectedNode = node;

  } else {

    appComp.selectedNode = null;

  }

});

} // end ngAfterViewInit

// initialize diagram / templates

public initDiagram(): go.Diagram 

{

  const $ = go.GraphObject.make;

  const dia = $(go.Diagram, {

    'undoManager.isEnabled': true,

    allowCopy: false,

    "draggingTool.dragsTree": true,

    "commandHandler.deletesTree": true,

    layout:

    $(go.TreeLayout,

      { angle: 90, arrangement: go.TreeLayout.ArrangementFixedRoots }),

    model: $(go.GraphLinksModel,

      {

        linkToPortIdProperty: 'toPort',

        linkFromPortIdProperty: 'fromPort',

        linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel

      }

    )

  });

  

  dia.commandHandler.archetypeGroupData = { key: 'Group', isGroup: true };

  const makePort = function(id: string, spot: go.Spot) {

    return $(go.Shape, 'Circle',

      {

        opacity: .5,

        fill: 'gray', strokeWidth: 0, desiredSize: new go.Size(8, 8),

        portId: id, alignment: spot,

        fromLinkable: true, toLinkable: true

      }

    );

  }

var bluegrad = $(go.Brush, "Linear", { 0: "#C4ECFF", 1: "#70D4FF" });

var greengrad = $(go.Brush, "Linear", { 0: "#B1E2A5", 1: "#7AE060" });

 // each action is represented by a shape and some text

 var actionTemplate =

 $(go.Panel, "Horizontal",

   $(go.Shape,

     { width: 12, height: 12 },

     new go.Binding("figure"),

     new go.Binding("fill")

   ),

   $(go.TextBlock,

     { font: "10pt Verdana, sans-serif" },

     new go.Binding("text")

   )

 );

// define the Node template

/*

dia.nodeTemplate =

  $(go.Node, 'Spot',

    {

      contextMenu:

        $('ContextMenu',

          $('ContextMenuButton',

            $(go.TextBlock, 'Group'),

            { click: function(e, obj) { e.diagram.commandHandler.groupSelection(); } },

            new go.Binding('visible', '', function(o) {

              return o.diagram.selection.count > 1;

            }).ofObject())

        )

    },

    $(go.Panel, 'Auto',

      $(go.Shape, 'RoundedRectangle', { stroke: null },

        new go.Binding('fill', 'color')

      ),

      $(go.TextBlock, { margin: 8 },

        new go.Binding('question'))

    ),

    // Ports

    makePort('t', go.Spot.TopCenter),

    makePort('l', go.Spot.Left),

    makePort('r', go.Spot.Right),

    makePort('b', go.Spot.BottomCenter)

  );

*/

  dia.nodeTemplate =  // the default node template

  $(go.Node, "Vertical",

  new go.Binding("isTreeExpanded").makeTwoWay(),  // remember the expansion state for

  new go.Binding("wasTreeExpanded").makeTwoWay(), //   when the model is re-loaded

  { selectionObjectName: "BODY" },

  // the main "BODY" consists of a RoundedRectangle surrounding nested Panels

  $(go.Panel, "Auto",

    { name: "BODY" },

    $(go.Shape, "Rectangle",

      { fill: bluegrad, stroke: null }

    ),

    $(go.Panel, "Vertical",

      { margin: 3 },

      // the title

      $(go.TextBlock,

        {

          stretch: go.GraphObject.Horizontal,

          font: "bold 12pt Verdana, sans-serif"

        },

        new go.Binding("text", "question")

      ),

      // the optional list of actions

      $(go.Panel, "Vertical",

        { stretch: go.GraphObject.Horizontal, visible: false },  // not visible unless there is more than one action

        new go.Binding("visible", "actions", function(acts) {

          return (Array.isArray(acts) && acts.length > 0);

        }),

        // headered by a label and a PanelExpanderButton inside a Table

        $(go.Panel, "Table",

          { stretch: go.GraphObject.Horizontal },

          $(go.TextBlock, "Choices",

            {

              alignment: go.Spot.Left,

              font: "10pt Verdana, sans-serif"

            }

          ),

          $("PanelExpanderButton", "COLLAPSIBLE",  // name of the object to make visible or invisible

            { column: 1, alignment: go.Spot.Right }

          )

        ), // end Table panel

        // with the list data bound in the Vertical Panel

        $(go.Panel, "Vertical",

          {

            name: "COLLAPSIBLE",  // identify to the PanelExpanderButton

            padding: 2,

            stretch: go.GraphObject.Horizontal,  // take up whole available width

            background: "white",  // to distinguish from the node's body

            defaultAlignment: go.Spot.Left,  // thus no need to specify alignment on each element

            itemTemplate: actionTemplate  // the Panel created for each item in Panel.itemArray

          },

          new go.Binding("itemArray", "actions")  // bind Panel.itemArray to nodedata.actions

        )  // end action list Vertical Panel

      )  // end optional Vertical Panel

    )  // end outer Vertical Panel

  ),  // end "BODY"  Auto Panel

  $(go.Panel,  // this is underneath the "BODY"

    { height: 17 },  // always this height, even if the TreeExpanderButton is not visible

    $("TreeExpanderButton")

  ),

  makePort('b', go.Spot.BottomCenter)

);

// define a second kind of Node:

dia.nodeTemplateMap.add("Terminal",

$(go.Node, "Spot",

  $(go.Shape, "Circle",

    { width: 55, height: 55, fill: greengrad, stroke: null }

  ),

  $(go.TextBlock,

    { font: "10pt Verdana, sans-serif" },

    new go.Binding("text")

  )

)

);



return dia;

}

/*.

public diagramNodeData: Array<go.ObjectData> = [

{ key: 'Alpha', text: "Node Alpha", color: 'lightblue' },

{ key: 'Beta', text: "Node Beta", color: 'orange' },

{ key: 'Gamma', text: "Node Gamma", color: 'lightgreen' },

{ key: 'Delta', text: "Node Delta", color: 'pink' }

];

*/

public initPalette(): go.Palette {

const $ = go.GraphObject.make;

const palette = $(go.Palette);

// define the Node template

palette.nodeTemplate =

  $(go.Node, 'Auto',

    $(go.Shape, 'RoundedRectangle',

      {

        stroke: null

      },

      new go.Binding('fill', 'color')

    ),

    $(go.TextBlock, { margin: 8 },

      new go.Binding('text'))

  );

palette.model = $(go.GraphLinksModel,

  {

    linkKeyProperty: 'key'  // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel

  });

return palette;

}

public paletteNodeData: Array<go.ObjectData> = [

{ key: 'PaletteNode1', text: "PaletteNode1", color: 'red' },

{ key: 'PaletteNode2', text: "PaletteNode2", color: 'yellow' }

];

public paletteLinkData: Array<go.ObjectData> = [

{  }

];

public paletteModelData = { prop: ‘val’ };

public paletteDivClassName = ‘myPaletteDiv’;

public skipsPaletteUpdate = false;

public paletteModelChange = function(changes: go.IncrementalData) {

// when setting state here, be sure to set skipsPaletteUpdate: true since GoJS already has this update

// (since this is a GoJS model changed listener event function)

// this way, we don't log an unneeded transaction in the Palette's undoManager history

this.skipsPaletteUpdate = true;

this.paletteNodeData = DataSyncService.syncNodeData(changes, this.paletteNodeData);

this.paletteLinkData = DataSyncService.syncLinkData(changes, this.paletteLinkData);

this.paletteModelData = DataSyncService.syncModelData(changes, this.paletteModelData);

};

// Overview Component testing

public oDivClassName = ‘myOverviewDiv’;

public initOverview(): go.Overview {

const $ = go.GraphObject.make;

const overview = $(go.Overview);

return overview;

}

public observedDiagram = null;

// currently selected node; for inspector

public selectedNode: go.Node | null = null;

public handleInspectorChange(newNodeData) {

const key = newNodeData.key;

// find the entry in nodeDataArray with this key, replace it with newNodeData

let index = null;

for (let i = 0; i < this.diagramNodeData.length; i++) {

  const entry = this.diagramNodeData[i];

  if (entry.key && entry.key === key) {

    index = i;

  }

}

if (index >= 0) {

  // here, we set skipsDiagramUpdate to false, since GoJS does not yet have this update

  this.skipsDiagramUpdate = false;

  this.diagramNodeData[index] = _.cloneDeep(newNodeData);

  // this.diagramNodeData[index] = _.cloneDeep(newNodeData);

}

// var nd = this.observedDiagram.model.findNodeDataForKey(newNodeData.key);

// console.log(nd);

}

}

Which ngAfterViewInit is not being called in that failure case? And do you know why not? I would think that’s an important clue that something has gone wrong. Somehow that needs to be debugged.

if i hardcode the data in ngOnInit it always work

this.diagramNodeData = [{“key”:1438,“question”:"–Kraft Freemont Intro Get Caller ID too",“actions”:null},{“key”:1439,“question”:“Get Employee ID”,“actions”:null}];
this.diagramLinkData = [{“from”:“1438”,“to”:“1439”,“answer”:“success”}];

However, if I fetch the data via API as in below in ngOnInit , it works sometimes and sometimes it does not work. My question is since its an async call once the data is fetched do I need to call
this.myDiagramComponent.modelChange.emit(); or do I need to do something else to let goJS diagram know that data has been fetched. I know once the API finishes it does log the correct data in console. I just need to know how to notify goJS diagram in this case. Hope that makes sense.

Thanks!

ngOnInit(): void {

let nodedata = this.nodeservice.getnodes(this.accountid);

let linksdata =  this.nodeservice.getlinks(this.accountid);

forkJoin([nodedata, linksdata]).subscribe(results => {

  this.diagramNodeData = results[0];

  this.diagramLinkData = results[1];

  console.log(this.diagramNodeData);

  console.log(this.diagramLinkData);

  this.myDiagramComponent.modelChange.emit();



});

console.log('ngonit ends');

}

Try setting a breakpoint in the code that merges data changes into the diagram’s model. It’s called mergeChanges, in the DiagramComponent, in gojs-angular, If that isn’t being called reliably when you have changes in your data, that’s a problem.