Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (Cyborg)
  • No Skin
Collapse
Diagram Community Logo
  1. Home
  2. GoJS
  3. Type-safe bindings in GoJS

Type-safe bindings in GoJS

Scheduled Pinned Locked Moved GoJS
1 Posts 1 Posters 66 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    Dropek
    wrote on last edited by
    #1

    Type-safe bindings

    Problem

    GoJS bindings are not typed as good as they could be, which can lead to issues when working with complex templates and many bindings. Both target and source properties can be any string, which isn't the biggest issue, but the real problem arises when managing numerous templates and bindings. Since these bindings rely on our node or link data, we must ensure that when a property name changes, all related bindings across the application are updated.

    Another limitation is that the converter method's first argument can be of any type, meaning we need to manually type it each time we initialize a binding. This can result in incorrect types. Worse, when we change the type of our node data, we may not realize that the bindings are incorrect until we encounter issues during testing. It would be ideal to catch these problems during development rather than at runtime.

    Solution

    In one of my projects, I implemented a small utility function that helps with typing, manages proper types, and alerts me when bindings need to be updated after modifying the types of node data. It also ensures that the converter logic is correct when the type of data changes (e.g., from an object to a string).

    First, we need to create a factory function to generate separate functions, each pre-typed based on our data. This is useful since we can have different types for node data, link data, or model data.

    export const createBindingBase =
      <N extends go.ObjectData>() =>
      <T extends keyof N>(
        target: string,
        source: T,
        converter: (arg: N[T], targetObj: go.GraphObject) => void
      ) =>
        new go.Binding(target, String(source), converter);
    

    In the code above, we created the createBindingBase method, which accepts a generic type representing our data. It returns the original go.Binding instance but ensures that the key of the data object is correct. Additionally, we no longer need to type the value in the converter method because it is inferred from our data object.

    Next, we can create a method for ourselves without needing to pass the generic type each time:

    type NodeData = {
      a: string;
      b: boolean;
    };
    
    export const createDataBinding = createBindingBase<NodeData>();
    

    Now, we can use this binding method easily:

    createDataBinding('fill', 'a', (a) => a); // Here typescript will know 'a' is a string
    createDataBinding('fill', 'b', (b) => b); // Here typescript will know 'b' is a boolean
    createDataBinding('fill', 'c', (c) => c); // Here typescript will throw an error
    

    This approach handles data bindings, but it would be useful to have similar behavior for model data. To achieve this, we need to call ofModel() on our binding. Here’s how we can extend the functionality:

    type CreateBindingParams<N extends go.ObjectData> = Parameters<
      ReturnType<typeof createBindingBase<N>>
    >;
    
    export const createModelBinding = (...args: CreateBindingParams<ModelData>) =>
      createBindingBase()(...args).ofModel();
    

    Now, we can use the createModelBinding method in the same way as createDataBinding, but it will bind to model data properties and infer types from them.

    Conclusion

    The utility functions for type-safe bindings are highly useful and can significantly speed up development by catching errors early, before we encounter them during runtime. It’s also convenient because we don't need to jump between type definitions and bindings to ensure everything is typed correctly.

    However, there are some limitations. For example, this solution doesn't handle all scenarios, such as two-way bindings. But it provides a solid starting point and can be extended to cover more use cases.

    1 Reply Last reply
    1

    • Login

    • Don't have an account? Register

    Powered by NodeBB Contributors
    • First post
      Last post
    0
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups