Issue with data not being updated when using a transaction

I’ve had a hard time reproducing this issue so I created a sample app (similar to the one of my other opend ticket and still based on the go-js-angular sample): Microsoft OneDrive - Access files anywhere. Create docs with free Office Online.

To reproduce :

  1. Launch the application
  2. Select a link
  3. In the inspector on the right type any string in the transitionType textbox and click away to trigger the focusOut event
  4. You will notice that the arrowFrom changes to a Circle (this is done in HandleInspectorChange)
  5. Deselect and reselect the link
  6. Notice that the text is gone; The property transitionType is now null
  7. Type some text again and click away.
  8. Deselect and reselect the link, this time the text is persisted

This is related to the call to :

 this.myDiagramComponent.diagram.model.commit((m) => {
          m.set(this.selectedNode.part.data, 'fromArrow', 'Circle');
        });

If this is commented, the text is always persisted.

Notice that we have almost the exact same issue with Node. If you change one of the properties in the node inspector, it will never be persisted, unless you comment :

this.myDiagramComponent.diagram.model.commit((m) => {

(not even calling m.set here…)

link to the sample app: Microsoft OneDrive - Access files anywhere. Create docs with free Office Online.

Could you please help me with this issue, I have no idea what’s going on :(

In the end I believe it is the same problem as the one in Taking action when dropping shape to diagram - #5 by graphicsxp

So it works if i do :

if (index >= 0) {

        this.diagramLinkData[index] = _.cloneDeep(newData);

         this.myDiagramComponent.diagram.model.commit((m) => {
          m.set(this.selectedNode.part.data, 'fromArrow', 'Circle');
          m.set(this.selectedNode.part.data, 'transitionType', newData.transitionType);
        });
       }

To me this is VERY confusing. I’ll explain why.

Before I did the dynamic binding for fromArrow, I only had to set properties that are not used for the actual rendering, such as transitionType, which is specific to my own business requirement.

So I was just doing

this.diagramLinkData[index] = _.cloneDeep(newData);

and it worked.

Now that I also have to set the fromArrow property, I had to add

this.myDiagramComponent.diagram.model.commit((m) => {
          m.set(this.selectedNode.part.data, 'fromArrow', 'Circle');
          m.set(this.selectedNode.part.data, 'transitionType', newData.transitionType);
        });

This works but why on earth do I have to do this, if cloning newData worked before ? Could you shine some light on this ?

Did you figure this out after learning more about data binding and models?

No, I didn’t.

I can’t understand why doing this :

this.diagramLinkData[index] = _.cloneDeep(newData);

works fine.
but when adding this:

this.myDiagramComponent.diagram.model.commit((m) => {
          m.set(this.selectedNode.part.data, 'fromArrow', 'Circle');          
        });

then transitionType is no longer persisted. Then to fix it, I have to do:

this.myDiagramComponent.diagram.model.commit((m) => {
          m.set(this.selectedNode.part.data, 'fromArrow', 'Circle');
          m.set(this.selectedNode.part.data, 'transitionType', newData.transitionType);
        });

somehow it makes sense to use model.set but i’m not sure why it is not required for transitionType and becomes mandatory when starting to use the transaction.

Hello,

I’ve tried to follow this as best as I can, perhaps I can shed some light on the situation.

The inspector should be changing app state, not the GoJS model. When this.diagramLinkData[index] is set to a deep clone of newData, Angular change detection within DiagramComponent notices and updates the GoJS model accordingly.

You probably should not be using any model methods in your handleInspectorChanged function. Instead, just replace the node/link data entry in app data (i.e. diagramLinkData or diagramNodeData), and ensure your Node / Link templates have bindings on the data properties you want to visually depend on these properties.

In your link case, simply stick with

this.diagramLinkData[index] = ._cloneDeep(newData);

which will persist the transitionType data property. Then, in your linkTemplate, create a data binding on your arrowhead go.Shape, something like

new go.Binding("fromArrow", "transitionType", (transitionType) => { // do some logic and return a string here }

Please let me know if any of this is not clear. State management with Angular can be a tricky beast. We’re working on an immutable gojs-angular version 2.0, but I can’t promise when it will come out. When it does, it will hopefully make this process more smooth

Interesting. Thanks for this explanations. So if I understand you correctly, I should do something like this :

 $(go.Shape,  // the arrowhead
              new go.Binding('toArrow', 'transitionType', (transitionType) => {
                switch (transitionType) {
                  case 'Linear':
                    return 'Standard';
                  default:
                    return '';
                }
              }),
            ),

and in the handleInspectorChange, I roll back to what I had initially :

 index = this.diagramLinkData.findIndex(n => n.key === key);
      if (index >= 0) {
        this.diagramLinkData[index] = _.cloneDeep(newData);

        // if shapes appearance should be modified from the inspector, it can be done like this
        // this.myDiagramComponent.diagram.model.commit((m) => {
        //   const link: any = this._processDesignerService.processDesignerMetadata.paletteLinks.find
        //     ((l: any) => l.transitionType === this.diagramLinkData[index].transitionType);
        //   if (link) {
        //     m.set(this.selectedNode.part.data, 'arrowheadFrom', link.arrowheadFrom);
        //     m.set(this.selectedNode.part.data, 'arrowheadTo', link.arrowheadTo);
        //     m.set(this.selectedNode.part.data, 'lineType', link.lineType);
        //   }
        //   // m.set(this.selectedNode.part.data, 'transitionType', newData.transitionType);
        //   // m.set(this.selectedNode.part.data, 'transitionRule', newData.transitionRule);
        // });
      }
    }

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

I have two problems with that. As you can see in the commented code, I was dynamically setting arrowheadFrom (etc…) based on some value found in this.diagramLinkData. Can I access diagramLinkData from the binding function when creating the link template ?

Second problem is that the function above with the switch is never hit. When I change a value of transiationType in the inspector, the handle function gets hit but not the binding function. Any idea why would that be ?

thanks for the help

You should not reference the Angular app-level diagramLinkData in your binding conversion function. But, by the time that conversion function is evaluated, your GoJS Diagram’s Model will be at parity with diagramLinkData, so you could use your the model.linkDataArray for your logic.

I’m surprised your binding function is not evaluating. I just tried with the sample you gave us and it evaluated fine. The entire code for the arrowhead shape in the template I tried is:

$(go.Shape,  { toArrow: "OpenTriangle" }, // the arrowhead
          new go.Binding('toArrow', 'transitionType', (transitionType, shape) => {
            // if you really want access to your diagram's linkDataArray, here's how you could access it
            var diagram = shape.part.diagram;
            var linkDataArray = diagram.model.linkDataArray; // do whatever logic you need with this            

            switch (transitionType) {
              case 'Linear':
                return 'Standard';
              default:
                return '';
            }
          })),

Does that make sense?

Yes it makes perfect sense. Sorry my bad regarding the function not being triggered, I was just doing something silly.

One last thing if you don’t mind. What if in the binding function above, instead of getting the data from linkDataArray, I want to get it from a property provided by an Angular service that is injected in the constructor of my main component. How would I do that ?
I was thinking about using that modelData property of the diagram. Could that hold arrays of lookup data which I could then access from the binding function in my switch statement ? Is this the correct approach ?

Generally, you want your bindings to fire when your a specific node or link model data property changes – it is strange to trigger a binding by changing one piece of data, then rely on some non-model properties. The more pieces of state you depend on for your binding the more careful you’ll have to be about when you update that underlying data property – what if the conversion function evaluates and you haven’t yet updated the other bits of state you want it to depend on?

Accessing Angular app-level properties in binding functions is not recommended – you will likely run into scope issues.

You can bind to modelData directly, if you want. Have you read this?
https://gojs.net/latest/intro/dataBinding.html#BindingToSharedModelDataSource

My requirement is to be able to change the appeance of links based on the value in a dropdown of the inspector (tranistionType)
Each transitionType holds values for arrowTo, arrowFrom, dash, stroke etc…

That’s why when the user select a transitionType from the dropdown, I must be able to change the appearnce of the selected link based on the properties of that transitionType.

The transitionType are coming from a service and now I have stored them in dataModel so that they are accessible from the diagram :

The following code works as expected now :

$(go.Shape,  // the arrowhead
          { fromArrow: '' }, //default value         
          new go.Binding('fromArrow', 'transitionType', (transitionType, shape) => {

            const diagram = shape.part.diagram;
            const linkDataArray = diagram.model.modelData.links;

            return linkDataArray.find(l => l.transitionType === transitionType).arrowheadFrom;
          })),

Does it make sense , or do you think this is the wrong way of doing things ?

That should be ok. My only suggestion would be to avoid using the variable name ‘linkDataArray’ to avoid confusion with the actual array of link data.