Infinite Loop When Starting A Transaction


I am experiencing a problem with transactions. When I add a node or link without using transactions, it adds perfectly.
However, using transactions, it starts an infinite loop and crashes.

My main purpose is to add some extra nodes when a node is added to diagram.

So, this is my modelChanged:

onModelChanged() {
    let me = this;
    return e => {
        if (e.isTransactionFinished) {
            let modelsToOperate = [];
            let txn = e.object;
            if (txn !== null) {
                txn.changes.each(function (ch) {
                    if (ch.change === go.ChangedEvent.Insert && ch.modelChange === "nodeDataArray") {
                        console.log("model changed");
                modelsToOperate.forEach(model => {

And this is the modelChangeOperations:
modelChangeOperations(newNode: any) {
this.diagram.startTransaction(“model change transaction”);
this.diagram.commitTransaction(“model change transaction”);

Even if I removed the logic in this method and does not add anything, it still starts an infinite loop.
Any help would be appreciated.
Thank you

You typically shouldn’t be making any changes to the model or any of its data in a change listener as that can lead to infinite loops. If you took out the creation of a new transaction during the listener, do you still get the loop?

What sort of logic do you want to use when adding a new node? There must be some cases where you won’t add a new node upon node creation. Also, when are new nodes being created within your diagrams?

Hi jhardy, thank you for quick response

I made my change after transaction finishes in modelChange, checking with if (e.isTransactionFinished)

Normally, my nodes are being created through dropping from palette. When some specific nodes are added, I must add extra nodes and connect them. For example, when user tries to add Natural Gas Engine from the palette, I must programmatically add Cooler and connect them.

I did not try to took out the creation of a new transaction. If not in the modelChange listener, where should I put the logic? Because I only detect node adding event by modelChange. Is there any other way you can suggests?

If the nodes are being dropped from the palette, maybe you want to use the “ExternalObjectsDropped” DiagramEvent instead. You can add a listener to add your new nodes/links upon dropping something from the palette. See more in the intro: GoJS Events -- Northwoods Software

When does ExternalObjectDropped is fired? Does it being fired after add node transaction finishes? Should I worry about automatic transaction for node addition? Thank you in advance.

Actually, since you know the conditions under which you want to create a new node, you should be able to use a model changed listener.

In this codepen, I’ve got a ModelChanged function that checks for the addition of new nodes to the model, and if the added node’s data has a certain property set, adds another node to the model. You’ll notice the call to addNodeDataCollection occurs after looping through the changes.

Hi jhardy, I sincerely appreciate your effort.

My logic is exactly same as yours and it works. However, I think that I should add nodes in a transaction (eg. between startTransaction() and commitTransaction()). Am I wrong?

Shouldn’t I call evt.model.addNodeDataCollection(nodesToAdd); in a transaction for the best practise? But when I add nodes in a transaction, infinite loop occurs and onModelChange perpetually detects nodeDataArray change.

Thank you

Sorry for going back and forth on this, but I think an ExternalObjectsDropped handler will be best for this. The event will fire after the node has already been dragged over and created in the diagram. It will already be wrapped in a “Drop” transaction, so you won’t need to conduct another one to add a new node. I’ve added a new codepen reflecting this behavior. The diagram initialization includes "ExternalObjectsDropped": onNodeAdded.

We sometimes do clever things with transactions where if one ends and another immediately starts, we may be bundling them together, so it’s best not to try making new data while iterating transactions in a model changed listener.