Immer producer returned a new value and modified its draft

Hi,
ChangeDetectorRef is imported and applied at ngAfterViewInit as well, but am getting this error as well ExpressionChangedAfterItHasBeenCheckedError and couldn’t overcome this issue… Where diagrams are appearing for some profile and some are not updated… Should I seperate the subscribe into ngOninit or in the constructor? Tried in ngOnInIt() and immer is not accepting with errors

But am getting the nodes in diagram view with this immer error… and these errors are on and off sometimes… Request your help on this…


  @ViewChild("resources", { static: true }) public resourceComponent: DiagramComponent;

  profile = this.ps.getRecord(this.ps.idx)
  profileId = this.profile.id;

  userData = [];

  constructor( private ps: PresentationService, private service: CoreService, private cdr: ChangeDetectorRef ) { }

  ngOnInit(): void { }

  fixedConference_url = environment.rpvUrl_gojs_lineconference_get
    .replace('{server_ip}', environment.server_ip)
    .replace('{server_port}', environment.server_port)
    .replace('{profileId}', this.profileId)

  profileUsers_url = environment.rpvUrl_gojs_profileUsers_get
    .replace('{server_ip}', environment.server_ip)
    .replace('{server_port}', environment.server_port)
    .replace('{profileId}', this.profileId) //Active PROFILE ID here

  // userNodes = this.service.getRecords(this.profileUsers_url).subscribe((result) => {
  //   this.userData = result;
  //   console.log(this.userData);
  // });


  public state: AppState = {
    diagramNodeData: [this.service.getRecords(this.profileUsers_url).subscribe((result) => {
      this.state.diagramNodeData = result.map(res => { return res });
      // this.state.diagramNodeData = this.userData
    }),
    ],

    diagramModelData: { prop: "value" },
    skipsDiagramUpdate: false,

  }
  public diagramDivClassName: string = "myDiagramDiv";
  public initDiagram(): go.Diagram {
    const $ = go.GraphObject.make;
    let dia = $(go.Diagram, {

      "undoManager.isEnabled": true,
      "animationManager.isEnabled": true,
      allowDragOut: false,

      model: new go.GraphLinksModel(
        {
          nodeKeyProperty: 'key',
          linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
        }
      ),
    });

    // Define a simple template consisting of the icon surrounded by a filled circle
    dia.nodeTemplate = $(
      go.Node,
      "Vertical",
      {
        resizable: false,
        resizeObjectName: "SHAPE",
        locationSpot: new go.Spot(0, 0),
      },

      // always save/load the point that is the top-left corner of the node, not the location
      new go.Binding("position", "pos", go.Point.parse).makeTwoWay(
        go.Point.stringify
      ),
      new go.Binding("text", "name"), //for sorting     

      $(
        go.Picture, // flag image, only visible if a nation is specified
        { visible: false, desiredSize: new go.Size(50, 50), margin: 10 },
        new go.Binding("source", "deviceCategoryID", imgTypeConverter),
        new go.Binding("source", "callTypeDialCode", imgTypeConverter),
        new go.Binding("visible", "deviceCategoryID", (nat) => nat !== undefined),
        new go.Binding("visible", "callTypeDialCode", (nat) => nat !== undefined),

        // new go.Binding("fill", "color"),
        new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(
          go.Size.stringify
        )
      ),
      $(
        go.TextBlock,
        {
          alignment: go.Spot.Bottom,
          alignmentFocus: go.Spot.BottomCenter,
          // margin: 2,
          maxSize: new go.Size(160, NaN),
          stroke: "white",
        },
        new go.Binding("text", "name"),
      ),
      {
        toolTip: $(
          "ToolTip",
          { "Border.stroke": "#5b5b5b", "Border.strokeWidth": 2 },
          $(
            go.TextBlock,
            { margin: 8, stroke: "#5b5b5b", font: "bold 16px sans-serif" },
            new go.Binding("text", "name"),
          )
        ),
      },

    ); //END of NODE Template

    return dia;
  };

  public resourcesDiagram: go.Diagram = null;
  public observedDiagram: go.Diagram = null;

  public selectedNodeData: go.ObjectData = null;

  public ngAfterViewInit() {
    this.cdr.detectChanges(); // IMPORTANT: without this, Angular will throw ExpressionChangedAfterItHasBeenCheckedError (dev mode only)
    // const RVPDComp: ResourcePlanningViewDiagramComponent = this;

    console.log("AT ngAfterViewINIT...")
    console.log(this.ps.getRecord(this.ps.idx));

    console.log(this.profile.name);
    console.log(this.profile.isActiveProfile);

    if (this.resourcesDiagram) return;
    this.resourcesDiagram = this.resourceComponent.diagram;
    console.log(this.state.diagramNodeData);

    if (this.observedDiagram) return;
    this.observedDiagram = this.resourceComponent.diagram;
  }



  // When the diagram model changes, update app data to reflect those changes
  public diagramModelChange(changes: go.IncrementalData) {
    if (!changes) return;
    const rpvComp = this;
    this.state = produce(this.state, (draft) => {
      draft.skipsDiagramUpdate = true;
      draft.diagramNodeData = DataSyncService.syncNodeData(
        changes,
        draft.diagramNodeData,
        rpvComp.observedDiagram.model
      );
      draft.diagramModelData = DataSyncService.syncModelData(
        changes,
        draft.diagramModelData
      );

    });
  }
}

function imgTypeConverter(type) {
  switch (type) {
    case Constant.DEVICE_CATEGORY_STI:
      console.log(type)
      return environment.image_path + "ST-I-button" + ".png";
    case Constant.DEVICE_CATEGORY_STX:
      console.log(type)
      return environment.image_path + "ST-X-button" + ".png";
    case Constant.DEVICE_CATEGORY_RADIO:
      console.log(type)
      return environment.image_path + "Radio-button" + ".png";
    case Constant.DEVICE_CATEGORY_PA:
      console.log(type)
      return environment.image_path + "Pa-button" + ".png";
    case Constant.CALLTYPE_DIALCODE_FCONF:
      console.log(type)
      return environment.image_path + "Conference-button" + ".png";
  }
}

And the stack trace is added below

resource-planning-view-diagram.component.ts:152 [SafeSubscriber]
logger.service.ts:10 ERROR: Error: [Immer] An immer producer returned a new value and modified its draft. Either return a new value or modify the draft.
at n (immer.esm.mjs:1:216)
at P (immer.esm.mjs:1:2508)
at produce (immer.esm.mjs:1:16338)
at Function.syncNodeData (gojs-angular.js:708:39)
at resource-planning-view-diagram.component.ts:166:31
at produce (immer.esm.mjs:1:16172)
at ResourcePlanningViewDiagramComponent.diagramModelChange (resource-planning-view-diagram.component.ts:164:25)
at ResourcePlanningViewDiagramComponent_Template_gojs_diagram_modelChange_10_listener (resource-planning-view-diagram.component.html:72:78)
at executeListenerWithErrorHandling (core.mjs:14979:16)
at Object.wrapListenerIn_markDirtyAndPreventDefault [as next] (core.mjs:15017:22)
error @ logger.service.ts:10
handleError @ custom-error.handler.ts:13
handleError @ core.mjs:10957
executeListenerWithErrorHandling @ core.mjs:14982
wrapListenerIn_markDirtyAndPreventDefault @ core.mjs:15017
next @ Subscriber.js:91
_next @ Subscriber.js:60
next @ Subscriber.js:31
(anonymous) @ Subject.js:34
errorContext @ errorContext.js:19
next @ Subject.js:27
emit @ core.mjs:22427
(anonymous) @ gojs-angular.js:135
_ZoneDelegate.invoke @ zone.js:409
onInvoke @ core.mjs:25548
_ZoneDelegate.invoke @ zone.js:408
Zone.run @ zone.js:169
run @ core.mjs:25402
component.modelChangedListener @ gojs-angular.js:128
push.1963.t.ws @ go-module.js:1799
push.1963.pe.Cb @ go-module.js:272
qe @ go-module.js:269
push.1963.t.Xa @ go-module.js:267
push.1963.Z.commit @ go-module.js:1803
mergeAppDataWithModel @ gojs-angular.js:154
ngOnChanges @ gojs-angular.js:261
rememberChangeHistoryAndInvokeOnChangesHook @ core.mjs:1508
callHook @ core.mjs:2552
callHooks @ core.mjs:2511
executeCheckHooks @ core.mjs:2443
refreshView @ core.mjs:9493
refreshComponent @ core.mjs:10655
refreshChildComponents @ core.mjs:9280
refreshView @ core.mjs:9534
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshComponent @ core.mjs:10655
refreshChildComponents @ core.mjs:9280
refreshView @ core.mjs:9534
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshComponent @ core.mjs:10655
refreshChildComponents @ core.mjs:9280
refreshView @ core.mjs:9534
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshComponent @ core.mjs:10655
refreshChildComponents @ core.mjs:9280
refreshView @ core.mjs:9534
renderComponentOrTemplate @ core.mjs:9598
tickRootContext @ core.mjs:10829
detectChangesInRootView @ core.mjs:10854
detectChanges @ core.mjs:21451
tick @ core.mjs:26490
(anonymous) @ core.mjs:26345
_ZoneDelegate.invoke @ zone.js:409
onInvoke @ core.mjs:25548
_ZoneDelegate.invoke @ zone.js:408
Zone.run @ zone.js:169
run @ core.mjs:25402
next @ core.mjs:26344
next @ Subscriber.js:91
_next @ Subscriber.js:60
next @ Subscriber.js:31
(anonymous) @ Subject.js:34
errorContext @ errorContext.js:19
next @ Subject.js:27
emit @ core.mjs:22427
checkStable @ core.mjs:25470
onLeave @ core.mjs:25598
onInvokeTask @ core.mjs:25542
_ZoneDelegate.invokeTask @ zone.js:442
Zone.runTask @ zone.js:214
ZoneTask.invokeTask @ zone.js:525
invokeTask @ zone.js:1714
globalCallback @ zone.js:1757
globalZoneAwareCallback @ zone.js:1781
load (async)
customScheduleGlobal @ zone.js:1865
_ZoneDelegate.scheduleTask @ zone.js:430
onScheduleTask @ zone.js:320
_ZoneDelegate.scheduleTask @ zone.js:423
Zone.scheduleTask @ zone.js:257
Zone.scheduleEventTask @ zone.js:283
(anonymous) @ zone.js:2022
(anonymous) @ http.mjs:1901
_trySubscribe @ Observable.js:37
(anonymous) @ Observable.js:31
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
doInnerSub @ mergeInternals.js:19
outerNext @ mergeInternals.js:14
(anonymous) @ OperatorSubscriber.js:13
next @ Subscriber.js:31
(anonymous) @ innerFrom.js:51
_trySubscribe @ Observable.js:37
(anonymous) @ Observable.js:31
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
mergeInternals @ mergeInternals.js:50
(anonymous) @ mergeMap.js:13
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:26
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
(anonymous) @ filter.js:6
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:26
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
(anonymous) @ map.js:6
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:26
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
(anonymous) @ tap.js:15
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:26
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
(anonymous) @ catchError.js:9
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:26
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
ResourcePlanningViewDiagramComponent @ resource-planning-view-diagram.component.ts:51
ResourcePlanningViewDiagramComponent_Factory @ resource-planning-view-diagram.component.ts:21
getNodeInjectable @ core.mjs:3565
instantiateAllDirectives @ core.mjs:10298
createDirectivesInstances @ core.mjs:9647
ɵɵelementStart @ core.mjs:14541
ɵɵelement @ core.mjs:14596
ContentComponent_div_9_div_2_div_10_Template @ content.component.html:129
executeTemplate @ core.mjs:9618
renderView @ core.mjs:9421
createEmbeddedView @ core.mjs:22685
createEmbeddedView @ core.mjs:22794
create @ common.mjs:3505
enforceState @ common.mjs:3513
ngDoCheck @ common.mjs:3687
callHook @ core.mjs:2552
callHooks @ core.mjs:2511
executeCheckHooks @ core.mjs:2443
selectIndexInternal @ core.mjs:8399
ɵɵadvance @ core.mjs:8388
ContentComponent_div_9_div_2_Template @ content.component.html:131
executeTemplate @ core.mjs:9618
refreshView @ core.mjs:9484
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshComponent @ core.mjs:10655
refreshChildComponents @ core.mjs:9280
refreshView @ core.mjs:9534
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshComponent @ core.mjs:10655
refreshChildComponents @ core.mjs:9280
refreshView @ core.mjs:9534
refreshEmbeddedViews @ core.mjs:10609
refreshView @ core.mjs:9508
refreshComponent @ core.mjs:10655
refreshChildComponents @ core.mjs:9280
refreshView @ core.mjs:9534
renderComponentOrTemplate @ core.mjs:9598
tickRootContext @ core.mjs:10829
detectChangesInRootView @ core.mjs:10854
detectChanges @ core.mjs:21451
tick @ core.mjs:26490
(anonymous) @ core.mjs:26345
_ZoneDelegate.invoke @ zone.js:409
onInvoke @ core.mjs:25548
_ZoneDelegate.invoke @ zone.js:408
Zone.run @ zone.js:169
run @ core.mjs:25402
next @ core.mjs:26344
next @ Subscriber.js:91
_next @ Subscriber.js:60
next @ Subscriber.js:31
(anonymous) @ Subject.js:34
errorContext @ errorContext.js:19
next @ Subject.js:27
emit @ core.mjs:22427
checkStable @ core.mjs:25470
onLeave @ core.mjs:25598
onInvokeTask @ core.mjs:25542
_ZoneDelegate.invokeTask @ zone.js:442
Zone.runTask @ zone.js:214
ZoneTask.invokeTask @ zone.js:525
invokeTask @ zone.js:1714
globalCallback @ zone.js:1745
globalZoneAwareCallback @ zone.js:1781
Show 194 more frames
Show less

I don’t understand this code:

What are those two Diagram variables supposed to be? Do you actually have two Diagrams (probably one Diagram and one Overview)? What do you want to show in your component? Why initialize those variables the same way, to the same object? Why stop if the first one is already non-null?

If you are using a GraphLinksModel, you also need to manage the GraphLinksModel.linkDataArray Array. That means using the diagramLinkData in the state and updating it in diagramModelChange, as the gojs-angular-basic sample demonstrates.

If you aren’t using the shared Model.modelData Object, remove all code related to that, particularly involving diagramModelData. The gojs-angular-basic sample project is a project that demonstrates a lot of features, not all of which your app may need.

Hi Walter,

Yes Overview diagram is used as reference or backup to see in console or in table format in html (only for reference)… And am trying to avoid it to for confusion and errors… And only resource diagram will be used in this component to focus on the diagram of resource management.

Am not having any links to be appeared in the diagram, hence there is no use of LinkDataArray… In this case how should I manage the model in the initDiagram?

And also removed diagramModelData…

If you don’t have any Links, that’s fine – you can leave the GraphLinksModel.linkDataArray empty and don’t bother with diagramLinkData.

I still don’t understand the circumstances in which you get the error, assuming you do still get the error.

While am leaving it empty for diagramLinkData = [ ], am getting LinkData can’t be empty Error…
Hence disabled the diagramModelData, ObservedDiagram and lastly due to immer error and ExpressionChangedAfterItHasBeenCheckedError now disabled the whole diagramModelChange(changes: go.IncrementalData)

Request your help to get the update function - diagramModelChange(changes), then shall add it in the html to be triggered upon change… Updated Code is as below… Hope understood the conversations and updated accordingly. Request to correct me if am wrong.

But now it looks ok without any errors only image picture is getting undefined and looking on it…

  @ViewChild("resources", { static: true }) public resourceComponent: DiagramComponent;

  profile = this.ps.getRecord(this.ps.idx)
  profileId = this.profile.id;

  userData = [];

  constructor( private ps: PresentationService, private service: CoreService, private cdr: ChangeDetectorRef ) { }

  ngOnInit(): void {   }

  fixedConference_url = environment.rpvUrl_gojs_lineconference_get
    .replace('{server_ip}', environment.server_ip)
    .replace('{server_port}', environment.server_port)
    .replace('{profileId}', this.profileId)

  profileUsers_url = environment.rpvUrl_gojs_profileUsers_get
    .replace('{server_ip}', environment.server_ip)
    .replace('{server_port}', environment.server_port)
    .replace('{profileId}', this.profileId) //Active PROFILE ID here

    userNodes = this.service.getRecords(this.profileUsers_url).subscribe((result) => {
      this.userData = result;
      console.log(this.userData);
    });


  // public state: AppState = {
  public state = {
    // diagramNodeData: [this.service.getRecords(this.profileUsers_url).subscribe((result) => {
    //   this.state.diagramNodeData = result.map(res => { return res });
    //   // this.state.diagramNodeData = this.userData
    // }),
    // ],
    // diagramLinkData: [],
    skipsDiagramUpdate: false,
  }
  public diagramDivClassName: string = "myDiagramDiv";
  public initDiagram(): go.Diagram {
    const $ = go.GraphObject.make;
    let dia = $(go.Diagram, {

      "undoManager.isEnabled": true,
      "animationManager.isEnabled": true,
      allowDragOut: false,

      model: new go.GraphLinksModel(
        {
          nodeKeyProperty: 'key',
          linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
        }
      ),
    });

    // Define a simple template consisting of the icon surrounded by a filled circle
    dia.nodeTemplate = $(
      go.Node,
      "Vertical",
      {
        resizable: false,
        resizeObjectName: "SHAPE",
        locationSpot: new go.Spot(0, 0),
      },       

      // always save/load the point that is the top-left corner of the node, not the location
      new go.Binding("position", "pos", go.Point.parse).makeTwoWay(
        go.Point.stringify
      ),
      new go.Binding("text", "name"), //for sorting     

      $(
        go.Picture, // flag image, only visible if a nation is specified
        { visible: false, desiredSize: new go.Size(50, 50), margin: 10 },
        new go.Binding("visible", "deviceCategoryID", (nat) => nat !== undefined),
        new go.Binding("visible", "callTypeDialCode", (nat) => nat !== undefined),
        new go.Binding("source", "deviceCategoryID", this.imgTypeConverter),
        new go.Binding("source", "callTypeDialCode", this.imgTypeConverter),

        // new go.Binding("fill", "color"),
        new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(
          go.Size.stringify
        )
      ),
      $(
        go.TextBlock,
        {
          alignment: go.Spot.Bottom,
          alignmentFocus: go.Spot.BottomCenter,
          // margin: 2,
          maxSize: new go.Size(160, NaN),
          stroke: "white",
        },
        new go.Binding("text", "name"),
      ),
      {
        toolTip: $(
          "ToolTip",
          { "Border.stroke": "#5b5b5b", "Border.strokeWidth": 2 },
          $(
            go.TextBlock,
            { margin: 8, stroke: "#5b5b5b", font: "bold 16px sans-serif" },
            new go.Binding("text", "name"),
          )
        ),
      },

    ); //END of NODE Template

    return dia;
  };

  public resourcesDiagram: go.Diagram = null;
  public selectedNodeData: go.ObjectData = null;
  // this.resourcesDiagram.model.
  public ngAfterViewInit() {
    this.cdr.detectChanges(); // IMPORTANT: without this, Angular will throw ExpressionChangedAfterItHasBeenCheckedError (dev mode only)
    // const RVPDComp: ResourcePlanningViewDiagramComponent = this;

    console.log("AT ngAfterViewINIT...")
    console.log(this.ps.getRecord(this.ps.idx));

    console.log(this.profile.name);
    console.log(this.profile.isActiveProfile);

    if (this.resourcesDiagram) return;
    this.resourcesDiagram = this.resourceComponent.diagram;
    // console.log(this.state.diagramNodeData);
    this.resourcesDiagram.model.nodeDataArray = this.userData;
    console.log(this.resourcesDiagram.model.nodeDataArray)  
  }

  // When the diagram model changes, update app data to reflect those changes
  // public diagramModelChange(changes: go.IncrementalData) {
  //   if (!changes) return;
  //   const rpvComp = this;
  //   this.state = produce(this.state, (draft) => {
  //     draft.skipsDiagramUpdate = true;
  //     draft.diagramNodeData = DataSyncService.syncNodeData(changes,
  //       draft.diagramNodeData,
  //       rpvComp.resourcesDiagram.model
  //     );
  //     // draft.diagramLinkData = DataSyncService.syncLinkData(changes, draft.diagramLinkData, rpvComp.resourcesDiagram);
  //   });
  // }

 imgTypeConverter(type) {
  switch (type) {
    // case Constant.DEVICE_CATEGORY_STI:
    //   console.log(type)
    //   return environment.image_path + "ST-I-button" + ".png";
    case Constant.DEVICE_CATEGORY_NTA3: //DEVICE_CATEGORY_STX:
      console.log(type)
      return environment.image_path + "ST-X-button" + ".png";
      // return "/assets/img/ST-X-button.png";
    case Constant.DEVICE_CATEGORY_RADIO:
      console.log(type)
      return environment.image_path + "Radio-button" + ".png";
    case Constant.DEVICE_CATEGORY_PA:
      console.log(type)
      return environment.image_path + "Pa-button" + ".png";
    case Constant.CALLTYPE_DIALCODE_FCONF:
      console.log(type)
      return environment.image_path + "Conference-button" + ".png";
  }
}

and the updated html is

          <gojs-diagram #resources [initDiagram]="initDiagram" [nodeDataArray]="this.userData"
            [divClassName]="diagramDivClassName" [(skipsDiagramUpdate)]="state.skipsDiagramUpdate">
            <!-- (modelChange)="diagramModelChange($event)" -->
          </gojs-diagram>
        </div>