Best practices for integrating third party libraries?

I’m trying to include this popular third party js tree library called inspire tree(GitHub - helion3/inspire-tree: Inspired Javascript Tree) and I’m having a hard time integrating this library for the following reason: I don’t know a good way to prevent ember from complaining that the inspire tree was not updated with ember.set. What I have done so far is create a service that holds a reference to the inspired tree

export default class GtOntologyTree extends Service {
  inspiredTree!: InspireTree;

  init() {
    super.init();
    this.set("inspiredTree", new InspireTree({}));
  }

  addEvent(this: GtOntologyTree, eventName: string, listener: Listener) {
    this.inspiredTree.on(eventName, listener);
  }

  addNodes(this: GtOntologyTree, nodes: Array<NodeConfig>) {
    this.inspiredTree.addNodes(nodes);
  }

  getNodes(this: GtOntologyTree) {
   return this.inspiredTree.nodes()
  }
}

Create Several Tree components to manage the rendering of the tree. The GtOntologyTreeNode component is the one with the problem since this is where several properties in the inspire tree node gets mutated by inspire-tree i.e the collapsed property, selected property etc and not by ember. I just don’t know a good way to resolve this issue because of how ember simply works.

export default class GtOntologyTreeNode extends Component {
  node!: TreeNode; // This is a node from inspire tree that is passed in.

  @action
  toggleSelect(this: GtOntologyTreeNode ) {
    this.node.toggleSelect(); // Mutates and will cause an error.
  }

  @action
  toggleCollapse(this: GtOntologyTreeNode ) {
    this.node.toggleCollapse(); // Mutates and will cause an error.
  }
}
import ontologies from "gt-query-ui-ember/data/ontologies"
import { bind } from '@ember/runloop';

export default class GtOntologyTree extends Component {
  nodes!: TreeNodes; 
  @service gtOntologytreeService!: GtOntologyTreeService
 
  didInsertElement(this: GtOntologyTree ) {
    this.gtOntologyTreeService.addNodes(ontologies);
   // Here is where the problem start. I need to be able to render these nodes but by 
   // storing them in this.nodes, ember will now watching for changes to all the objects
   // in this.node.
    this.set("nodes", this.gtOntologyTreeService.getNodes());
    // changes.applied is a event that gets emitted when ever there
    // is a change to the tree.
    this.gtOntologyTreeService.addEvent("changes.applied", bind(this,  this.treeUpdated))
  }

  treeUpdated(this: GtOntologyTree) {
    this.set("nodes", this.gtOntologyTreeService.getNodes());
    this.notifyPropertyChange(this.nodes);
  }
}

Any ideas would be appreciated.

1 Like

What’s in the template that goes with class GtOntologyTree?

Does it do something like {{#each this.nodes as |node|}}? If so, that is why Ember really does need to be told when the nodes list is changing. Otherwise there would be no way to cause newly added nodes to render.

You can take responsibility for notifying when the node list might have changed. One way to do that is to make it a computed property with no dependencies:

// This goes in your Service
@computed()
get nodes() {
  return this.inspiredTree.nodes()
}

Then where you consume it in the template, say it like:

{{#each this.gtOntologytreeService.nodes as |node|}}

instead of grabbing its value only once at didInsertElement.

Finally, wherever you call the library APIs that can mutate the node list, also call this.notifyPropertyChange('nodes') on your Service.

It’s not clear from your example whether you really need a Service. If there’s always going to be a topmost GtOntologyTree component, it can own the InspireTree directly.

Thanks for the reply man.

Yes the template in the class GtOntologyTree is exactly as you described. The reason why I made a service is that If decide that I dont want to use inspired tree because down the road I decide to switch for some other javascript tree library, I only have to modify the service. At least that what I think but I take the things that you’ve said into consideration.

Your suggestion is great but it still leaves the big dilemma where ember complains that the node is getting updated without calling ember.set

so when I do {{#each this.gtOntologytreeService.nodes as |node|}} and pass node down to gt-ontology-tree-node, then ember is going to be watching for any changes to the node object. This is the biggest problem I’m having with using this third party. What can I do so that ember doesn’t complain when the tree gets modified by this third party library without calling ember.set?

I see that TreeNode has a toObject() method that turns it into a POJO. That should let you grab your own copy of the data that won’t be mutated underneath you, and then you can update your copy by listening to InspireTree’s events.

This can all be done in one reusable component any place you’re accessing a TreeNode.

1 Like