Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Preston Lamb
Preston Lamb

Posted on • Originally published atMedium on

     

Introducing the @ngneat/dag Library

tldr;

If you need to build a workflow of some sort, a good way to organize the model is by using a directed acyclic graph. DAG models can be used to store a lot of information, and they can be somewhat complicated. Essentially, the data is represented by vertices (which I call nodes throughout this article) and edges. The edges connect one node to another, and go from earlier to later in the sequence. This model works perfectly for building a workflow, and in this article you'll see how to use the@ngneat/dag library to manage a DAG model in your Angular application.

Library Installation and Setup(#library-installation-setup)

Before we get started, install the library in your Angular application:

$npminstall @ngneat/dag
Enter fullscreen modeExit fullscreen mode

OR

$yarn add @ngneat/dag
Enter fullscreen modeExit fullscreen mode

After the library is installed in the application, you're ready to implement it in your application. The library consists of a service (which does all the heavy lifting) and an interface. The service is a little different than many services in Angular apps, and that's because it's not meant to be provided in the root of the application. When services are provided that way, they are instantiated as singletons, and the data in the service is available to any component or service in the application. In this case though, the DAG model doesn't need to be persisted outside the life of the component used to build the workflow. So, to use the service, you need to include it in a component'sproviders array:

// workflow-builder.component.tsimport{DagManagerService}from'@ngneat/dag';@Component({selector:'app-workflow-builder',templateUrl:'',styleUrls:[],providers:[DagManagerService]})
Enter fullscreen modeExit fullscreen mode

If you don't remember this step, you'll get an error in your application about there not being a provider for the service. After including the library in the providers array, the next step is to inject the service into your component with dependency injection, just like any other Angular service. Before doing that though, you need to define an interface or class that extends theDagModelItem interface from the library. TheDagModelItem interface defines a few attributes that are required to be on each item in the DAG model array. The attributes on the interface arestepId,parentIds, andbranchPath. The interface or class you define can have any other number of attributes you want:

// workflow-step.interface.tsexportinterfaceWorkflowStepextendsDagModelItem{databaseId:number;name:string;data:any;}
Enter fullscreen modeExit fullscreen mode

Then, back in the workflow builder component:

// workflow-builder.component.tsimport{DagManagerService}from'@ngneat/dag';import{WorkflowStep}from'./workflow-step.interface.ts';exportclassWorkflowBuilderComponent{constructor(private_dag:DagMangerService<WorkflowStep>){}}
Enter fullscreen modeExit fullscreen mode

When you declare the service that you're injecting in the constructor, you need to provide the interface or class that you defined for your DAG model inside the angle brackets. This helps the library have more information about the model by using generics. Again, if you don't do this step, you'll see errors in your application and IDE.

At this point, you're ready to get into using the library, and all the setup is done.

Database Storage

TheDagManagerService converts an array of items into a two dimensional array that represents the DAG model for displaying it. The best way to store it in your database though is in a single dimensional array. Your backend data service should provide the workflows as a single dimensional array as well when loading the workflow. TheDagManagerService can convert the single dimensional array to the two dimensional DAG display model, as well as converting that back to a single dimensional array for you.

ngOnInit(){// Providing the service with items to convert to the DAG modelthis._dagManager.setNewItemsArrayAsDagModel(this.startingItems);// Converting the DAG model to a single dimensional arrayconstitemsArray=this._dagManager.getSingleDimensionalArrayFromModel();}
Enter fullscreen modeExit fullscreen mode

These two arrays allow you to get the data from the server and provide the starting array to the service, as well as getting the latest model and flattening out the two dimensional array for sending to the database.

Creating and Managing a DAG Model

Now that the library service is installed and configured, you're ready to start using it. The first thing you should do in thengOnInit method of the component is to set the nextstepId that the service will use when creating new steps. Each step in the DAG model should be unique. If you're starting with a brand new workflow, the next number would be 1. If you're loading a workflow, the best way to determine the next number is to loop over all the items that come back from the database and find the max value forstepId. Adding one to that max value will ensure that you don't replicatestepId values. You can set the next number in the service like this:

// workflow-builder.component.tsngOnInit(){constnextNumber=this.determineNextNumber();this._dag.setNextNumber(nextNumber);}
Enter fullscreen modeExit fullscreen mode

The next thing you should do after setting the nextstepId number to use is to set the DAG model in the service. You can do that with any single dimensional array ofWorkflowSteps like this:

ngOnInit(){// this.startingItems can be an array retrieved from a backend, or a new array if you're creating a new workflowthis._dag.setNewItemsArrayAsDagModel(this.startingItems)}
Enter fullscreen modeExit fullscreen mode

The service will then have an array which will be converted to the two dimensional DAG model and to which items can be added or from which they can be removed. You can gain access to the DAG model Observable from the service like this:

publicdagModel$:Observable<WorkflowStep[][]>;ngOnInit(){this.dagModel$=this._dag.dagModel$;}
Enter fullscreen modeExit fullscreen mode

By using that Observable, your application, or UI, can update automatically any time an item is added or removed.

Speaking of adding items, there are two ways of adding items to the model. One of the ways automatically updates the Observable, and the other way returns the items in a single dimension array. The easiest way is to have the service automatically update the Observable. To add an item to the model, you can use theaddNewStep method. This method takes 4 parameters:

  • the parentIds that the new item(s) are related to;
  • the number of new children to add;
  • the branch path to start with (this will likely almost always be 1);
  • and an object with all the attributes forWorkflowStepexcept the attributes fromDagModelItem. Here's an example:
doAddStep(){this._dag.addNewStep([1],1,1,{name:'',id:null});}
Enter fullscreen modeExit fullscreen mode

The above function adds one new item with abranchPath of 1, with theparentId array containing a single number (meaning it's a child ofstepId 1). Thename attribute on the new item will be an empty string, and theid will benull.

If you want to remove a node from the model, you only need to pass the ID of the item which needs to be removed. If you want to removestepId 1, you can do so like this:

doAddStep(){this._dag.removeStep(1);}
Enter fullscreen modeExit fullscreen mode

Both of these methods will be really useful as you manage the model for your application. To read more about other ways to add or remove items from the model,check out the documentation. You can alsoplay with the demo application to get a feel for how it works.

Tips for Displaying the Model

One of the hardest parts when I was creating this service was trying to figure out how to display the workflow on the page. I figured though that the more I could use default functionality in Angular (i.e.*ngFor) and CSS (i.e. flexbox), the better. That's the reasoning behind thedagModel$ Observable being a two dimensional array. This made it so that all that I needed to do to output the model was to nest an*ngFor loop inside another*ngFor loop. The first loop outputs each row in the model, working from top down. The second loop outputs the columns, from left to right. You can see a detailed example of this in thedemo app in the repository.

In addition, you can use theleader-line package to draw lines between nodes to show the flow of the model. You can readthis article to learn how to do that, and also check the above mentioned demo app. The key is that each time the model is updated, or more items are output to the screen, all the leader lines are removed and then redrawn. Again, the demo app will help you see how to do this.

Conclusion

I'm really excited about the possibilities for this library. We're using it on a project at work, but wanted it open sourced so that we could get some help from the community and get some ideas on what can be added. Check it out, try it, and submit feedback. Hopefully it helps out in your project as well

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Preston Lamb is a full stack JavaScript developer, Angular GDE, ngChampion writer for the ng-conf blog, & co-organizer of the Angular Community Meetup.
  • Location
    Roy, UT
  • Education
    BS in Computer Science from Utah State University
  • Work
    Software Developer at MotivHealth
  • Joined

More fromPreston Lamb

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp