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.

I’m also facing similar issue, did anyone get the resolution for this, in my case it mostly happens when, I make any code change in angular app, on reload, it sometimes doesnt render the graph also, when the browser is out of focus, it happens, im pretty sure we havent written any logic to stop renderring the graph in any case.

That sounds like a caching problem. I’ve seen that in other auto-reloading environments. I assume the problem goes away when force-reloading the page?