ak-tree-view

Tree view component for angular

Usage no npm install needed!

<script type="module">
  import akTreeView from 'https://cdn.skypack.dev/ak-tree-view';
</script>

README

Tree View

A tree view component for Angular.

Getting started

Import TreeModule

import { TreeModule } from "ak-tree-view";

@NgModule({
  declarations: [AppComponent],
  imports: [CommonModule, TreeModule],
  bootstrap: [AppComponent],
})

Example

TS file

import { Component, ViewChild } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";
import {
  TreeViewComponent,
  TreeConfig,
  DropEvent,
  TreeNode,
} from "ak-tree-view";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.scss"],
})
export class AppComponent {
  @ViewChild(TreeViewComponent) tree!: TreeViewComponent;

  readonly STORAGE_KEY = "tree_state";
  public config: TreeConfig = {
    lazyChildrenLoader: this.getChildren.bind(this),
    canDrag: this.canDrag.bind(this),
    canDrop: this.canDrop.bind(this),
    onCollapse: this.onCollapse.bind(this),
    onExpand: this.onExpand.bind(this),
    hideToggler: false,
    expandAfterDrop: false,
    showDragHandler: true,
    slotOverTimeOut: 50,
    nodePadding: 40,
    branchline: true,
    branchlinePadding: 15,
  };

  public nodes = [
    {
      name: "root1",
      isExpanded: false,
      children: [{ name: "child1" }, { name: "child2" }],
    },
    {
      name: "root2",
      isExpanded: false,
      children: [
        { name: "child2.1" },
        { name: "child2.2", children: [{ name: "child2.2.1" }] },
      ],
    },
    { name: "root3", hasLazyChildren: true },
    { name: "root4" },
    { name: "root5", children: [] },
  ];

  public providers = [
    {
      name: "Folder",
      children: [],
    },
    {
      name: "File",
    },
  ];

  save(): void {
    this.tree.toJSON().then((json) => {
      localStorage.setItem(this.STORAGE_KEY, JSON.stringify(json));
      alert("state saved to local storage !");
    });
  }

  edit(): void {
    this.tree.toggleEditMode();
  }

  add(): void {
    this.tree.addNode({ name: "New node" });
  }

  update(node: TreeNode): void {
    node.data = { name: "New name" };
  }

  load(): void {
    const json = localStorage.getItem(this.STORAGE_KEY);
    if (!json) {
      return;
    }

    this.tree.fromJSON(JSON.parse(json));
  }

  onDrop($event: DropEvent): void {
    console.log("Drop event...", $event);
  }

  getChildren(treeNode: TreeNode): Promise<any[]> {
    return new Promise((resolve) => {
      // To simulate API call
      setTimeout(() => {
        resolve(this.nodes);
      }, 2000);
    });
  }

  // Example (Optional)
  canDrag(treeNode: TreeNode): boolean {
    return true;
  }

  // Example (Optional)
  canDrop(treeNode: TreeNode): boolean {
    return true;
  }

  // Example (Optional)
  public onCollapse(treeNode: TreeNode): void {
    console.log("collapsed", treeNode);
  }

  // Example (Optional)
  public onExpand(treeNode: TreeNode): void {
    console.log("expanded", treeNode);
  }
}

HTML file

<div class="app-container">
  <div class="app-content">
    <div class="app-content-buttons">
      <button (click)="save()">Save state</button>
      <button (click)="load()">load state</button>
      <button (click)="edit()">edit mode</button>
      <button (click)="add()">add node</button>
    </div>
    <div class="app-content-tree">
      <tree-view
        #treeView
        [nodes]="nodes"
        [config]="config"
        (drop)="onDrop($event)"
      >
        <ng-template #node let-node let-index="index">
          <div class="custom-node">
            <div class="left">{{ node.data.name }} {{ index }}</div>
            <div class="option">
              <button (click)="update(node)">Update</button>
            </div>
          </div>
        </ng-template>
        <ng-template #loading>
          <small>My custom children loading...</small>
        </ng-template>
      </tree-view>
    </div>
  </div>
  <div class="app-list">
    <div class="app-list-providers">
      <div
        treeNodeToTransfert
        [tree]="treeView"
        [data]="item"
        class="app-list-node"
        *ngFor="let item of providers"
      >
        {{ item.name }}
      </div>
    </div>
  </div>
</div>

SCSS file

tree-view {
  position: relative;
  z-index: 1;
  .tree-node {
    &-container {
      background: white;
      border-radius: 5px;
      border: 1px solid rgb(207, 207, 207);

      &-left {
        display: flex;
        align-items: center;
      }
    }
    &-content {
      padding: 10px;
    }
    &-parent {
      font-weight: 600;
      &:hover {
        cursor: pointer;
      }
    }
  }

  .custom-node {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    .option {
      &:hover {
        cursor: s-resize;
      }
    }
  }
}
.app {
  &-container {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-around;
    width: 100%;
    height: 100%;
  }
  &-content {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    &-buttons {
      margin-bottom: 10px;
      button {
        margin: 0px 5px;
      }
    }
    &-tree {
      background-color: rgb(226, 226, 226);
      overflow: auto;
      width: 608px;
      height: 674px;
      position: relative;
      padding: 10px;
      &-grid {
        z-index: 0;
        position: absolute;
        width: 100%;
        height: 100%;
        display: flex;
        .line {
          border-left: 1px solid rgb(202, 202, 202);
          margin-left: 40px;
          height: 100%;
        }
      }
    }
  }
  &-list {
    background-color: rgb(224, 224, 224);
    padding: 20px;
    overflow: auto;
    width: 208px;
    height: 674px;
    &-node {
      display: flex;
      align-items: center;
      background-color: white;
      border: 1px solid rgb(189, 189, 189);
      border-radius: 5px;
      padding: 10px;
      margin: 5px 0px;
      &:hover {
        cursor: grab;
      }
    }
  }
}


.indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: orangered;
  width: 30px;
  height: 41px;
  margin-right: 8px;
  color: white;
  font-weight: bold;
}

.tree-node-drop-slot {
  height: 5px;
  transition: height 0.25s ease-in;
  &.active {
    height: 40px !important;
  }
}

.tree-view-root.empty {
  .tree-node-drop-slot {
    height: 40px;
  }
}

.option {
  display: flex;
  align-items: center;
}
.moving-state.dragover {
  .tree-node-container {
    background: rgba(2, 235, 2, 0.356);
  }
}

.dragover .tree-node-container {
  background: rgba(2, 235, 2, 0.356);
}