Introducing the @ngneat/dag Library
Tags: angular, javascript, ngneat, programming
Published: 2021-02-17 16:34:54

Preston Lamb | ng-conf | Dec 2020

### **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**
Before we get started, install the library in your Angular application:

```
$ npm install @ngneat/dag
```

OR

```
$ yarn add @ngneat/dag
```

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’s `providers` array:

```
// workflow-builder.component.ts
import { DagManagerService } from ‘@ngneat/dag’;
@Component({
  selector: ‘app-workflow-builder’,
  templateUrl: ‘’,
  styleUrls: [],
  providers: [DagManagerService]
})
```

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 the `DagModelItem` interface from the library. The `DagModelItem` interface defines a few attributes that are required to be on each item in the DAG model array. The attributes on the interface are `stepId`, `parentIds`, and `branchPath`. The interface or class you define can have any other number of attributes you want:

```
// workflow-step.interface.ts
export interface WorkflowStep extends DagModelItem {
  databaseId: number;
  name: string;
  data: any;
}
```

Then, back in the workflow builder component:

```
// workflow-builder.component.ts
import { DagManagerService } from ‘@ngneat/dag’;
import { WorkflowStep } from ‘./workflow-step.interface.ts’;
export class WorkflowBuilderComponent {
  constructor(private _dag: DagMangerService<WorkflowStep>) {}
}
```

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, all the setup is complete and you’re ready to start using the library.

### **Database Storage**
The `DagManagerService` 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. The `DagManagerService` 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 model
  this._dagManager.setNewItemsArrayAsDagModel(this.startingItems);
// Converting the DAG model to a single dimensional array
  const itemsArray = 
    this._dagManager.getSingleDimensionalArrayFromModel();
}
```

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 the `ngOnInit` method of the component is to set the next `stepId` 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 for `stepId`. Adding one to that max value will ensure that you don’t replicate `stepId` values. You can set the next number in the service like this:

```
// workflow-builder.component.ts
ngOnInit() {
  const nextNumber = this.determineNextNumber();
  this._dag.setNextNumber(nextNumber);
}
```

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

```
ngOnInit() {
  // this.startingItems can be an array retrieved from a backend
  // or a new array if you’re creating a new workflow
this._dag.setNewItemsArrayAsDagModel(this.startingItems)
}
```

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 as an Observable from the service like this:

```
public dagModel$: Observable<WorkflowStep[][]>;
ngOnInit() {
  this.dagModel$ = this._dag.dagModel$;
}
```

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 the `addNewStep` 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 for `aWorkflowStep` _except_ the attributes from `DagModelItem`.

Here’s an example:

```
doAddStep() {
  this._dag.addNewStep([1], 1, 1, { name: ‘’, id: null });
}
```

The above function adds one new item with a `branchPath` of 1, with the `parentId` array containing a single number (meaning it’s a child of `stepId` 1). The `name` attribute on the new item will be an empty string, and the `id` will be `null`.

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 remove `stepId` 1, you can do so like this:

```
doAddStep() {
  this._dag.removeStep(1);
}
```

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](https://github.com/ngneat/dag). You can also [play with the demo application](https://ngneat-dag-demo.netlify.app/) 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 the `dagModel$` 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 the [demo app in the repository](https://github.com/ngneat/dag).

In addition, you can use the `leader-line` package to draw lines between nodes to show the flow of the model. You can read [this article](https://medium.com/ngconf/using-leader-line-to-draw-lines-between-two-angular-components-71a7c316a163) 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!

## **ng-conf: The Musical is coming**
ng-conf: The Musical is a two-day conference from the ng-conf folks coming on April 22nd & 23rd, 2021. Check it out at [ng-conf.org](https://www.2021.ng-conf.org/)