/ng2-tree-build

Build of ng2-tree

Primary LanguageCSS

🌿 ng2-tree

npm Travis Codecov

🎬 Usage

Ok, let's start with an installation - all you need to do is:

npm install --save ng2-tree

Now when you have ng2-tree installed, you are in a few steps from having tree in your application:

  1. Add the TreeModule to your application's module imports section:
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { BrowserModule } from '@angular/platform-browser';
import { TreeModule } from 'ng2-tree';

@NgModule({
  declarations: [MyComponent],
  imports:      [BrowserModule, TreeModule],
  bootstrap:    [MyComponent]
})
export class MyModule {
}
  1. As soon as the previous step is done we need to give ng2-tree a model to render - this can be accomplished by populating its [tree] attribute with an object that conforms to the TreeModel interface (see API):
// 1 - import required classes and interfaces
import { TreeModel } from 'ng2-tree';

@Component({
  selector: 'myComp',
  // 2 - set [tree] attribute to tree object
  template: `<tree [tree]="tree"></tree>`
})
class MyComponent {
  // 3 - make sure that tree object conforms to the TreeModel interface
  public tree: TreeModel = {
    value: 'Programming languages by programming paradigm',
    children: [
      {
        value: 'Object-oriented programming',
        children: [
          {value: 'Java'},
          {value: 'C++'},
          {value: 'C#'}
        ]
      },
      {
        value: 'Prototype-based programming',
        children: [
          {value: 'JavaScript'},
          {value: 'CoffeeScript'},
          {value: 'Lua'}
        ]
      }
    ]
  };
}
  1. Apart from that, in order to have usable tree in the browser, you need to add ng2-tree styles which you can find in your node_modules/ng2-tree/styles.css
  2. And finally, I suppose, you'd want to listen to events generated by ng2-tree (for a full list of supported events look at the API). No problem, this is also easy to do - for example let's add a listener for node was selected kind of events:
// 1 - import required classes and interfaces
import { TreeModel, NodeEvent } from 'ng2-tree';

@Component({
  selector: 'myComp',
  // 2 - listent for nodeSelected events and handle them
  template: `<tree [tree]="tree" (nodeSelected)="logEvent($event)"></tree>`
})
class MyComponent {
  public tree: TreeModel = { ... };

  // 3 - print caught event to the console
  public logEvent(e: NodeEvent): void {
    console.log(e);
  }
}

Voila! That's pretty much it - enjoy 😊

👀 Demo

Feel free to examine the demo and its sources to find out how things are wired. Also there is another demo built with Angular CLI.

🔧 API

Here is the fully stuffed tree tag that you can use in your templates:

    <tree
      [tree]="tree"
      [settings]="settings"
      (nodeRemoved)="handleRemoved($event)"
      (nodeRenamed)="handleRenamed($event)"
      (nodeSelected)="handleSelected($event)"
      (nodeMoved)="handleMoved($event)"
      (nodeCreated)="handleCreated($event)"
      (nodeExpanded)="handleExpanded($event)"
      (nodeCollapsed)="handleCollapsed($event)">
    </tree>

Let's go through every element of this structure one by one.

tree

tree is the selector for TreeComponent which is bundled into TreeModule:

[tree]

tree has a [tree] attribute which needs to be populated with an object implementing TreeModel interface. You can import this interface like below:

import { TreeModel } from 'ng2-tree';

Here is the definition of the TreeModel interface:

interface TreeModel {
  value: string | RenamableNode;
  id: string | number;
  children?: Array<TreeModel>;
  loadChildren?: ChildrenLoadingFunction;
  settings?: TreeModelSettings;
}

As you can see - object that conforms to this interface has a recursive nature, example can be seen below:

{
    value: 'Programming languages by programming paradigm',
    children: [
      {
        value: 'Object-oriented programming',
        children: [
          {value: 'Java'},
          {value: 'C++'},
          {value: 'C#'}
        ]
      },
      {
        value: 'Prototype-based programming',
        children: [
          {value: 'JavaScript'},
          {value: 'CoffeeScript'},
          {value: 'Lua'}
        ]
      }
    ]
  }

Property value can be of type string or RenamableNode. RenamableNode gives you an additional control over the way node is renamed and rendered (by rendered I mean its text representation). Here is the definition of the RenamableNode interface:

interface RenamableNode {
  // This method will be invoked in order to apply new value to this kind of node
  setName(name: string): void;

  // This method will be invoked in order to get a text for rendering as a node value
  toString(): string;
}

Here is an example of such a node in the TreeModel object:

{
    value: 'Programming languages by programming paradigm',
    children: [
      {
        value: 'Object-oriented programming',
        children: [
          {
            // I am a RenamableNode. Yeah, that's me :)
            value: <RenamableNode>{
              name: 'Java',
              setName(name: string): void {
                this.name = name;
              },
              toString(): string {
                return this.name;
              }
            }
          },
          {value: 'C++'},
          {value: 'C#'}
        ]
      },
      {
        value: 'Prototype-based programming',
        loadChildren: (callback) => {
          setTimeout(() => {
            callback([
              {value: 'JavaScript'},
              {value: 'CoffeeScript'},
              {value: 'TypeScript'}
            ]);
          }, 5000);
        }
      }
    ]
  };

Load children asynchronously

Another worth noting thing is loadChildren. This function on TreeModel allows you to load its children asynchronously.

{
  value: 'Prototype-based programming',
  loadChildren: (callback) => {
    setTimeout(() => {
      callback([
        {value: 'JavaScript'},
        {value: 'CoffeeScript'},
        {value: 'TypeScript'}
      ]);
    }, 5000);
  }
}

Node that defines this function is collapsed by default. At the moment of clicking 'Expand' arrow it starts loading its children by calling given function. If loadChildren function is given to the node - children property is ignored. For more details - have a look at the Demo.

Configure node via TreeModelSettings

Apart from that TreeModel interface has an optional field called settings of type TreeModelSettings.

Here is an example of its usage:

{
  value: 'Prototype-based programming',
  settings: {
    'static': true,
    'rightMenu': true,
    'leftMenu': true,
    'cssClasses': {
      'expanded': 'fa fa-caret-down fa-lg',
      'collapsed': 'fa fa-caret-right fa-lg',
      'leaf:': 'fa fa-lg',
      'empty': 'fa fa-caret-right disabled'
    },
    'templates': {
      'node': '<i class="fa fa-folder-o fa-lg"></i>',
      'leaf': '<i class="fa fa-file-o fa-lg"></i>',
      'leftMenu': '<i class="fa fa-navicon fa-lg"></i>'
    }
  },
  children: [
    {value: 'JavaScript'},
    {value: 'CoffeeScript'},
    {value: 'Lua'}
  ]
}
  • static - Boolean - This option makes it impossible to drag a tree or modify it in a some way, though you still can select nodes in the static tree and appropriate events will be generated.
  • rightMenu - Boolean - This option allows you to activate (true, by default) or deactivate (false) right menu when clicking with right button of a mouse.
  • leftMenu - Boolean - This option allows you to activate (true) or deactivate (false, by default) left menu.
  • cssClasses - Object:
    • expanded - String - It specifies a css class (or classes) for an item which represents expanded state of a node. The item is clickable and it transitions the node to the collapsed state
    • collapsed - String - It specifies a css class (or classes) for an item which represents collapsed state of a node. The item is clickable and it transitions the node to the expanded state
    • leaf - String - It specifies a css class (or classes) for an item which represents a node without an option to expand or collapse - in other words: a leaf node.
    • empty - String - Node is considered empty when it has no children. Once this condition is satisfied - appropriate css class will be applied to the node.
  • templates - Object:
    • node - String - It specifies a html template which will be included to the left of the node's value.
    • leaf - String - It specifies a html template which will be included to the left of the leaf's value.
    • leftMenu - String - It specifies a html template to the right of the node's value. This template becomes clickable and shows a menu on node's click.

All options that's defined on a parent are automatically applied to children. If you want you can override them by settings of the child node.

[settings]

Object that should be passed to [settings] must be of type Ng2TreeSettings. This attribute is optional. Right now only one setting is available in there - rootIsVisible. This setting allows you to make a root node of the tree invisible:

const treeSettings: Ng2TreeSettings = {
  rootIsVisible: false
}

By default rootIsVisible equals to true

Tree class

Also in the next section you'll be reading about events generated by the ng2-tree. And here Tree class comes in handy for us, because its instances propagated with event objects. Under the hood ng2-tree wraps a TreeModel provided by the user in Tree. And Tree in turn has lots of useful methods and properties (like parent, hasChild(), isRoot() etc.)

events (nodeMoved, nodeSelected, nodeRenamed, nodeRemoved, nodeCreated, nodeExpanded, nodeCollapsed)

Here is the diagram that shows tree events' hierarchy

tree events hierarchy

NodeEvent is the root of the tree events' hierarchy. It defines property node that contains a receiver of the event action (node is an instance of the Tree class).

NodeDestructiveEvent is the parent for all events that cause changes to the structure of the tree or to the node's value.

NodeSelectedEvent

You can subscribe to the NodeSelectedEvent by attaching listener to the (nodeSelected) attribute

    <tree
      [tree]="tree"
      (nodeSelected)="handleSelected($event)">
    </tree>

NodeSelectedEvent has just one property node which contains a Tree object representing selected node.

{node: <Tree>{...}}

NodeMovedEvent

You can subscribe to NodeMovedEvent by attaching listener to (nodeMoved) attribute

    <tree
      [tree]="tree"
      (nodeMoved)="handleMoved($event)">
    </tree>

NodeMovedEvent has two properties node and previousParent both of which contain Tree objects:

  • node contains a moved node;
  • previousParent contains a previous parent of the moved node;
{node: <Tree>{...}, previousParent: <Tree>{...}}

NodeRemovedEvent

You can subscribe to NodeRemovedEvent by attaching listener to (nodeRemoved) attribute

    <tree
      [tree]="tree"
      (nodeRemoved)="handleRemoved($event)">
    </tree>

NodeRemovedEvent has a node property, which contains removed node (of type Tree).

{node: <Tree>{...}}

NodeCreatedEvent

You can subscribe to NodeCreatedEvent by attaching listener to (nodeCreated) attribute

    <tree
      [tree]="tree"
      (nodeCreated)="handleCreated($event)">
    </tree>

NodeCreatedEvent has a node property of type Tree, which contains a created node and a controller property, which will give you access to node's controller.

{node: <Tree>{...}, controller: <TreeController>{...}}

NodeRenamedEvent

You can subscribe to NodeRenamedEvent by attaching listener to (nodeRenamed) attribute

    <tree
      [tree]="tree"
      (nodeRenamed)="handleRenamed($event)">
    </tree>

NodeRenamedEvent has three properties:

  • node contains node that was renamed (instance of Tree).
  • oldValue contains a value, that node used to have (it might be string or RenamableNode)
  • newValue contains a new value of the node (it might be string or RenamableNode)
{
  node: <Tree>{...},
  oldValue: <string|RenamableNode>{...},
  newValue: <string|RenamableNode>{...}
}

NodeExpandedEvent

You can subscribe to NodeExpandedEvent by attaching listener to (nodeExpanded) attribute, this event wont fire on initial expansion

    <tree
      [tree]="tree"
      (nodeExpanded)="handleExpanded($event)">
    </tree>

NodeExpandedEvent has a node property of type Tree, which contains an expanded node.

{node: <Tree>{...}}

NodeCollapsedEvent

You can subscribe to NodeCollapsedEvent by attaching listener to (nodeCollapsed) attribute

    <tree
      [tree]="tree"
      (nodeCollapsed)="handleCollapsed($event)">
    </tree>

NodeCollapsedEvent has a node property of type Tree, which contains a collapsed node.

{node: <Tree>{...}}

🔫 Controller

Fist of all you should know how to get a controller of a particular node. You can take a controller of a node only if you set id property of a node. For example your tree structure should look like:

public tree: TreeModel = {
    value: 'Programming languages by programming paradigm',
    id: 1,
    children: [
      {
        value: 'Object-oriented programming',
        id: 2,
        children: [
          {value: 'Java', id: 3},
          {value: 'C++', id: 4},
          {value: 'C#', id 5},
        ]
      },
      {
        value: 'Prototype-based programming',
        id: 6,
        children: [
          {value: 'JavaScript', id: 7},
          {value: 'CoffeeScript', id: 8},
          {value: 'Lua', id: 9},
        ]
      }
    ]
  };

Ids should be unique within one tree, otherwise some controllers will be overwrite and you wont be able to take them. In order to get a node's controller you shoud add hash tag to tree tag like this:

<tree [tree]="tree" #treeComponent></tree>

and you will be able to access the controller like this this.treeComponent.getControllerByNodeId(6)

Here are description and an example of how to use all methods of a node's controller:

let oop = this.treeComponent.getControllerByNodeId(2);

select - selecting a node

oop.select();

This method selects the node and reselect all other nodes, also it fires select event, so it will call your nodeSelected function if you have one.

isSelected - check if a node is selected

oop.isSelected();

This method returns true if the node is selected and false if it isn't.

collapse - collapsing a node

oop.collapse();

This method collapses the node in case it can be collapsed. On successful collapsing the collapse event is fired.

isCollapsed - check if a node is collapsed

oop.isCollapsed();

This method returns true if the node is collapsed and false if it isn't.

expand - expanding a node

oop.expand();

This method expands the node in case it can be expanded. On successful expanding the expand event is fired.

isExpanded - check if a node is expanded

oop.isExpanded();

This method returns true if the node is expanded and false if it isn't.

rename - change value of a node

oop.rename('new value');

This method accepts a string and set it to the node's name, this will also fires rename event.

remove - remove a node

oop.remove();

This method will remove the node and it's children and it will fire remove event.

addChild - create a child node

let newNode: TreeModel = {
  value: 'Go',
  children: []
};
oop.addChild(newNode);

This method accepts a TreeModel and add it as a children of the node or as a sibling.

changeNodeId - change node's id

oop.changeNodeId(10);

This method can change the node's id. When the user creates a node from node's menu you will access the new node after it's created and this method will provide a way to changed node's id.

reloadChildren - execute loadChildren function

oop.reloadChildren();

This method executes loadChildren if there is such a function provided in the TreeModel.

setChildren - change a node's children

let newChildren: Array<TreeModel> = [
  {value: 'new children 1'},
  {value: 'new children 2'},
  {value: 'new children 3'}
];
oop.setChildren(newChildren);

This method replace all existing children of the node with new ones.

Changes that should be taken into account in order to migrate from ng2-tree V1 to ng2-tree V2

  • Events were reworked:
    • In V1 all events that were inherited from NodeDestructiveEvent used to have property parent. It's not the case anymore. If you need a parent you should get it from node in event object like node.parent;
    • All events used to have node property of type TreeModel. Now node is of type Tree (as well as node.parent);
    • NodeMovedEvent now has property previousParent, which contains tree in which moved node used to be.
  • CSS styles in ng2-tree V2 are distributed as separate file which you can find in node_modules/ng2-tree/styles.css. That allows you to override ng2-tree styles more easely.

💡 Want to help?

I am very appreciate for your ideas, proposals and found bugs which you can put in github issues. Thanks in advance!

P.S. If you find it hard going through documentation, please, let me know which parts of it was difficult to grasp and I will improve them.