Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for component method as callback to diagram events #2

Closed
anuj9196 opened this issue Jan 14, 2020 · 8 comments
Closed

Support for component method as callback to diagram events #2

anuj9196 opened this issue Jan 14, 2020 · 8 comments

Comments

@anuj9196
Copy link

Adding angular's component member function as callback to context menu button click is giving error

TypeError: `functionName` is not a function

The stackblitz example: https://stackblitz.com/edit/angular-gojs-family-tree

I have added two menu for explanation where first menu is working fine but the second one where callback method is used is not working.

I also tried to bind the callback to this context using

click: this.buttonCallback.bind(this)

But error is still there.

Same issue is being faced where external methods are being used to return some value to the diagram properties.

Ex.,

$(go.TextBlock, this.getStyle(), new go.Binding('text', 'name'));

private getStyle() {
   return {
        font: '700 12px Droid Serif, sans-serif',
        textAlign: 'center',
        margin: 10, maxSize: new go.Size(80, NaN)
   };
}
@rjohnson465
Copy link
Contributor

Yes, that's because of the scope those functions are being called in -- they are being called from DiagramComponent, not AppComponent, so this.buttonCallback is being evaluated as DiagramComponent.buttonCallback, which of course does not exist. The easiest thing to do here is define your buttonCallback function within your initDiagram function.

@anuj9196
Copy link
Author

But then this is limiting the Angular features.

I tried to access data member of the component inside the working button callback method defined in the initDiagram, but again it gives not a function or undefined error. (Updated same stackblitz example)

Use case

There may be cases where I need to use the data members for further actions like storing the received data in a variable to use somewhere else or open a popup modal, etc.

Even the component's members are not accessible from inside the initDiagram.

Also about the 2nd issue, what if I have to calculate the font-size or the margin, etc dynamically and then assign to the go element?

@rjohnson465
Copy link
Contributor

I see what you're saying. In modern Angular / React, data flow always moves down, from parent to child, so it is unusual to want to reference properties of a parent component (in your case, wanting to reference properties of AppComponent from DiagramComponent, which is where initDiagram is firing).

Generally, all functions you depend on within initDiagram must be defined within initDiagram, as I said before. However, if there is some other data somewhere in your app that those functions must depend on, I recommend you store that in modelData, one of the Input properties DiagramComponent supports. This way, if you change modelData in your app, it will change in DiagramComponent's diagram, so then any function that references that property of modelData will also be effected.

For example, say you want to console.log some property in your app when your buttonCallback fires. Let's call that property 'color.' Change / add that property in your modelData object that you are already binding to your DiagramComponent. In your stackblitz example, you could define it like:

public diagramModelData = { prop: 'value', color: 'red' };

(Also, I see you bound 'familyData' to the [modelData] of your DiagramComponent, you should bind diagramModelData instead)

Then, within initDiagram, define your buttonCallback as something like

function buttonCallback(e, obj) {
console.log(e.diagram.model.modelData.color);
}

If anywhere in your app, you change the value of diagramModelData.color, that change will be reflected in your DiagramComponent's diagram.model.modelData.color, so your callback function will always reflect what your app data is

@anuj9196
Copy link
Author

anuj9196 commented Jan 16, 2020

@rjohnson465 Thanks for the explanation. The [modelData] binding was there due to a bug in #1 which prevents rendering of the diagram.

Updated the gojs-angular version and the example as well and the assigned properties to diagramModelData are working fine.

In my case, on click event, I want to open a modal to change the respected value.

constructor(
  private modal: NgbModal
) {}

initDiagram() {
  ...
  click: (e, obj) => {
    this.modal.open(EditNodeComponent, {centered: true});
    this.modal.componentInstance.data = e;
    this.modal.result(res => {
        // res is the updated data, update the same in the family tree
        // also send data in the backend using the service
    }
  }
  ...
}

In the above example, I have to access the modal instance which is binded to the this context. Since this is not accessible from inside the initDiagram method definition, modal and other properties or service instances can not accessed.

May be this issue is related to gojs instead of gojs-angular.

@rjohnson465
Copy link
Contributor

Again, this is an issue of scope, and how generally, data flow goes from parent to child, not the other way around.

One way to get around this is to set your click handler in your AppComponent's ngAfterViewInit() function. Make sure you have a reference to your DiagramComponent inside your AppComponent (by use of ViewChild) and then do something like

@ViewChild('myDiagram', { static: true }) public myDiagramComponent: DiagramComponent;
public ngAfterViewInit() {
    const appComp: AppComponent = this;
    this.myDiagramComponent.diagram.nodeTemplate.click = function(e, obj) {
      appComp.testLog();
    }
  } // end ngAfterViewInit

  public testLog() {
    console.log("test");
  }

@anuj9196
Copy link
Author

anuj9196 commented Jan 17, 2020

Thanks @rjohnson465 It's working this way and this context is also accessible.

Here is what I have done

@ViewChild('myDiag', {static: false}) myDiag: DiagramComponent;

ngAfterViewInit() {
  const $ = go.GraphObject.make;
  const appComp: AppComponent = this;
  this.myDiag.diagram.nodeTemplate.contextMenu = 
    $('ContextMenu', 
      $('ContextMenuButton',
        $(go.TextBlock, 'Working Button'),
        {
          click: (e, obj) => {
            appComp.buttonCallback(e, obj);
          }
        }
      )
    );
  }

buttonCallback(e, obj) {
  console.log('e2: ', e.diagram.model.modelData.color);
  console.log('this object: ', this.name);
}

@rjohnson465
Copy link
Contributor

That's right, since you're calling appComp.buttonCallback, "this" will refer to AppComponent. Glad it's working for you!

@NGabriela
Copy link

NGabriela commented Nov 22, 2021

I was able to make it possible to access any property of my enclosing component inside initDiagram by using initDiagram.bind(this).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants