tree

A hierarchical tree view for displaying nested data structures with expand/collapse, selection, and checkboxes.

Previous
package.json
tsconfig.json
README.md
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { provideIcons } from '@ng-icons/core';
import { lucideFile, lucideFolder } from '@ng-icons/lucide';

import { ZardTreeImports } from '@/shared/components/tree/tree.imports';
import type { TreeNode } from '@/shared/components/tree/tree.types';

@Component({
  selector: 'z-demo-tree-basic',
  imports: [ZardTreeImports],
  template: `
    <z-tree [zData]="fileSystem" class="w-full max-w-sm" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [provideIcons({ lucideFolder, lucideFile })],
})
export class ZardDemoTreeBasicComponent {
  readonly fileSystem: TreeNode<unknown>[] = [
    {
      key: 'src',
      label: 'src',
      icon: 'lucideFolder',
      children: [
        {
          key: 'app',
          label: 'app',
          icon: 'lucideFolder',
          children: [
            { key: 'app.component.ts', label: 'app.component.ts', icon: 'lucideFile', leaf: true },
            { key: 'app.component.html', label: 'app.component.html', icon: 'lucideFile', leaf: true },
            { key: 'app.module.ts', label: 'app.module.ts', icon: 'lucideFile', leaf: true },
          ],
        },
        {
          key: 'assets',
          label: 'assets',
          icon: 'lucideFolder',
          children: [{ key: 'logo.svg', label: 'logo.svg', icon: 'lucideFile', leaf: true }],
        },
        { key: 'main.ts', label: 'main.ts', icon: 'lucideFile', leaf: true },
        { key: 'index.html', label: 'index.html', icon: 'lucideFile', leaf: true },
      ],
    },
    {
      key: 'package.json',
      label: 'package.json',
      icon: 'lucideFile',
      leaf: true,
    },
    {
      key: 'tsconfig.json',
      label: 'tsconfig.json',
      icon: 'lucideFile',
      leaf: true,
    },
    {
      key: 'README.md',
      label: 'README.md',
      icon: 'lucideFile',
      leaf: true,
    },
  ];
}

Installation

Copy
npx zard-cli@latest add tree

Usage

import { ZardTreeImports } from '@/shared/components/tree/tree.imports';
Copy
<z-tree [data]="treeData"></z-tree>
Copy

Examples

basic

package.json
tsconfig.json
README.md
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { provideIcons } from '@ng-icons/core';
import { lucideFile, lucideFolder } from '@ng-icons/lucide';

import { ZardTreeImports } from '@/shared/components/tree/tree.imports';
import type { TreeNode } from '@/shared/components/tree/tree.types';

@Component({
  selector: 'z-demo-tree-basic',
  imports: [ZardTreeImports],
  template: `
    <z-tree [zData]="fileSystem" class="w-full max-w-sm" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [provideIcons({ lucideFolder, lucideFile })],
})
export class ZardDemoTreeBasicComponent {
  readonly fileSystem: TreeNode<unknown>[] = [
    {
      key: 'src',
      label: 'src',
      icon: 'lucideFolder',
      children: [
        {
          key: 'app',
          label: 'app',
          icon: 'lucideFolder',
          children: [
            { key: 'app.component.ts', label: 'app.component.ts', icon: 'lucideFile', leaf: true },
            { key: 'app.component.html', label: 'app.component.html', icon: 'lucideFile', leaf: true },
            { key: 'app.module.ts', label: 'app.module.ts', icon: 'lucideFile', leaf: true },
          ],
        },
        {
          key: 'assets',
          label: 'assets',
          icon: 'lucideFolder',
          children: [{ key: 'logo.svg', label: 'logo.svg', icon: 'lucideFile', leaf: true }],
        },
        { key: 'main.ts', label: 'main.ts', icon: 'lucideFile', leaf: true },
        { key: 'index.html', label: 'index.html', icon: 'lucideFile', leaf: true },
      ],
    },
    {
      key: 'package.json',
      label: 'package.json',
      icon: 'lucideFile',
      leaf: true,
    },
    {
      key: 'tsconfig.json',
      label: 'tsconfig.json',
      icon: 'lucideFile',
      leaf: true,
    },
    {
      key: 'README.md',
      label: 'README.md',
      icon: 'lucideFile',
      leaf: true,
    },
  ];
}

checkable

Checked: None

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardTreeImports } from '@/shared/components/tree/tree.imports';
import type { TreeNode } from '@/shared/components/tree/tree.types';

@Component({
  selector: 'z-demo-tree-checkable',
  imports: [ZardTreeImports],
  template: `
    <z-tree [zData]="permissions" zCheckable (zCheckChange)="onCheckChange($event)" class="w-full max-w-sm" />
    <p class="text-muted-foreground mt-4 text-sm">Checked: {{ checkedLabels }}</p>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoTreeCheckableComponent {
  checkedLabels = 'None';

  readonly permissions: TreeNode<unknown>[] = [
    {
      key: 'admin',
      label: 'Administration',
      children: [
        {
          key: 'users',
          label: 'User Management',
          children: [
            { key: 'users-create', label: 'Create Users', leaf: true },
            { key: 'users-edit', label: 'Edit Users', leaf: true },
            { key: 'users-delete', label: 'Delete Users', leaf: true },
          ],
        },
        {
          key: 'roles',
          label: 'Role Management',
          children: [
            { key: 'roles-create', label: 'Create Roles', leaf: true },
            { key: 'roles-edit', label: 'Edit Roles', leaf: true },
          ],
        },
      ],
    },
    {
      key: 'content',
      label: 'Content',
      children: [
        { key: 'content-view', label: 'View Content', leaf: true },
        { key: 'content-edit', label: 'Edit Content', leaf: true },
        { key: 'content-publish', label: 'Publish Content', leaf: true },
      ],
    },
  ];

  onCheckChange(nodes: TreeNode<unknown>[]) {
    const labels = nodes.filter(n => n.leaf).map(n => n.label);
    this.checkedLabels = labels.length ? labels.join(', ') : 'None';
  }
}

selection

Selected: None

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { provideIcons } from '@ng-icons/core';
import { lucideMonitor, lucideSmartphone, lucideTablet, lucideTag } from '@ng-icons/lucide';

import { ZardTreeImports } from '@/shared/components/tree/tree.imports';
import type { TreeNode } from '@/shared/components/tree/tree.types';

@Component({
  selector: 'z-demo-tree-selection',
  imports: [ZardTreeImports],
  template: `
    <z-tree
      [zData]="categories"
      zSelectable
      (zSelectionChange)="onSelect($event)"
      (zNodeClick)="onNodeClick($event)"
      class="w-full max-w-sm"
    />
    <p class="text-muted-foreground mt-4 text-sm">Selected: {{ selectedLabel }}</p>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [
    provideIcons({
      lucideMonitor,
      lucideSmartphone,
      lucideTablet,
      lucideTag,
    }),
  ],
})
export class ZardDemoTreeSelectionComponent {
  selectedLabel = 'None';

  readonly categories: TreeNode<unknown>[] = [
    {
      key: 'electronics',
      label: 'Electronics',
      icon: 'lucideMonitor',
      children: [
        {
          key: 'phones',
          label: 'Phones',
          icon: 'lucideSmartphone',
          children: [
            { key: 'iphone', label: 'iPhone', leaf: true },
            { key: 'samsung', label: 'Samsung Galaxy', leaf: true },
            { key: 'pixel', label: 'Google Pixel', leaf: true },
          ],
        },
        {
          key: 'laptops',
          label: 'Laptops',
          icon: 'lucideTablet',
          children: [
            { key: 'macbook', label: 'MacBook Pro', leaf: true },
            { key: 'thinkpad', label: 'ThinkPad', leaf: true },
          ],
        },
      ],
    },
    {
      key: 'clothing',
      label: 'Clothing',
      icon: 'lucideTag',
      children: [
        { key: 'mens', label: "Men's", leaf: true },
        { key: 'womens', label: "Women's", leaf: true },
      ],
    },
  ];

  onSelect(nodes: TreeNode<unknown>[]) {
    this.selectedLabel = nodes.map(n => n.label).join(', ') || 'None';
  }

  onNodeClick(_node: TreeNode<unknown>) {
    // Handle node click
  }
}

virtual scroll

3100 total nodes with virtual scrolling

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { provideIcons } from '@ng-icons/core';
import { lucideFile, lucideFolder } from '@ng-icons/lucide';

import { ZardTreeImports } from '@/shared/components/tree/tree.imports';
import type { TreeNode } from '@/shared/components/tree/tree.types';

@Component({
  selector: 'z-demo-tree-virtual-scroll',
  imports: [ZardTreeImports],
  template: `
    <z-tree
      [zData]="largeTree"
      zVirtualScroll
      [zVirtualItemSize]="32"
      zExpandAll
      class="h-[400px] w-full max-w-md rounded-md border"
    />
    <p class="text-muted-foreground mt-4 text-sm">{{ nodeCount }} total nodes with virtual scrolling</p>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [provideIcons({ folder: lucideFolder, file: lucideFile })],
})
export class ZardDemoTreeVirtualScrollComponent {
  readonly largeTree: TreeNode<unknown>[];
  readonly nodeCount: number;

  constructor() {
    this.largeTree = this.generateTree(100, 3);
    this.nodeCount = this.countNodes(this.largeTree);
  }

  private generateTree(breadth: number, depth: number, prefix = '', level = 0): TreeNode<unknown>[] {
    if (depth === 0) {
      return [];
    }
    return Array.from({ length: breadth }, (_, i) => {
      const key = prefix ? `${prefix}-${i}` : `${i}`;
      const children = depth > 1 ? this.generateTree(Math.min(breadth, 5), depth - 1, key, level + 1) : [];
      return {
        key,
        label: children.length > 0 ? `Folder ${key}` : `File ${key}`,
        icon: children.length > 0 ? 'lucideFolder' : 'lucideFile',
        leaf: children.length === 0,
        children: children.length > 0 ? children : undefined,
      };
    });
  }

  private countNodes(nodes: TreeNode<unknown>[]): number {
    let count = 0;
    for (const node of nodes) {
      count++;
      if (node.children) {
        count += this.countNodes(node.children);
      }
    }
    return count;
  }
}

API Reference

z-treeComponent

The root container for a hierarchical tree view.

PropertyDescriptionTypeDefault
[class] Custom CSS classes string ''
[zData] Tree data source TreeNode<T>[] []
[zSelectable] Enable node selection on click boolean false
[zCheckable] Enable checkbox selection with propagation boolean false
[zExpandAll] Expand all nodes initially boolean false
[zVirtualScroll] Enable virtual scrolling for large trees boolean false
[zVirtualItemSize] Virtual scroll item height in pixels number 32
(zNodeClick) Node clicked TreeNode<T> -
(zNodeExpand) Node expanded TreeNode<T> -
(zNodeCollapse) Node collapsed TreeNode<T> -
(zSelectionChange) Selection changed TreeNode<T>[] -
(zCheckChange) Checked nodes changed TreeNode<T>[] -
github iconwhatsapp icondiscord iconX icon

Made with in Brazil. Open source and available on GitHub .