Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ngx-json-treeview

A collapsible JSON tree view for Angular
A simple Angular component to display JSON data in an expandable tree view.

<img width="530" alt="image" src="https://github.com/user-attachments/assets/0312d9e8-6774-45ad-8610-71582055fbef" />

Expand All @@ -10,12 +10,39 @@ A collapsible JSON tree view for Angular
npm install ngx-json-treeview
```

## Key Features

- Expandable/collapsible nodes.
- Configurable initial expansion state and depth.
- Optional click handling for value nodes.

## Usage

To render JSON in its fully expanded state.

```html
<ngx-json-treeview [json]="someObject" />
```

To render JSON with all nodes collapsed.

```html
<ngx-json-treeview [json]="someObject" [expanded]="false" />
```

Alternatively, expand only to a max depth by default.

```html
<ngx-json-treeview [json]="someObject" [depth]="1" />
```

You can enable the user to click on values. Provide `onValueClick` to implement
the desired behavior.

```html
<ngx-json-treeview [json]="someObject" [enableClickableValues]="true" (onValueClick)="onValueClick($event)" />
```

## Demo

[https://stackblitz.com/edit/ngx-json-treeview](https://stackblitz.com/edit/ngx-json-treeview?file=src%2Fapp%2Fapp.component.ts)
Expand Down
18 changes: 18 additions & 0 deletions projects/demo/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,21 @@ <h3>Fully Expanded</h3>
<div class="json-container">
<ngx-json-treeview [json]="json" />
</div>

<h3>Clickable Nodes</h3>
<div class="clickable-container">
<div class="json-container">
<ngx-json-treeview
[json]="json"
[expanded]="false"
[enableClickableValues]="true"
(onValueClick)="onValueClick($event)" />
</div>

@let segment = currentSegment();
@if (segment) {
<div class="preview-pane">
<pre>{{ stringify(segment.value) }}</pre>
</div>
}
</div>
19 changes: 19 additions & 0 deletions projects/demo/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
.json-container {
font-family: monospace;
overflow: hidden;
padding: 10px;
}

.clickable-container {
display: flex;
gap: 10px;
width: 100%;
}

.clickable-container > .json-container,
.clickable-container > .preview-pane {
box-sizing: border-box;
flex-basis: 50%;
}

.preview-pane {
border-left: 1px solid #ccc;
padding: 10px;
}
14 changes: 12 additions & 2 deletions projects/demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { NgxJsonTreeviewComponent } from 'ngx-json-treeview';
import { Component, signal } from '@angular/core';
import { NgxJsonTreeviewComponent, Segment } from 'ngx-json-treeview';

@Component({
selector: 'app-root',
Expand All @@ -8,6 +8,8 @@ import { NgxJsonTreeviewComponent } from 'ngx-json-treeview';
styleUrl: './app.component.scss',
})
export class AppComponent {
currentSegment = signal<Segment | undefined>(undefined);

baseObj = {
string: 'Hello World',
number: 1234567890,
Expand Down Expand Up @@ -39,4 +41,12 @@ export class AppComponent {
return 'baz';
},
};

onValueClick(segment: Segment) {
this.currentSegment.set(segment);
}

stringify(obj: any) {
return typeof obj === 'function' ? '' + obj : JSON.stringify(obj, null, 2);
}
}
35 changes: 20 additions & 15 deletions projects/ngx-json-treeview/src/lib/ngx-json-treeview.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@
@for (segment of segments(); track segment) {
<section [class]="'segment segment-type-' + segment.type">
@let expandable = isExpandable(segment);
<section
(click)="toggle(segment)"
[ngClass]="{
'segment-main': true,
expandable: expandable,
expanded: segment.expanded,
}">
@if (expandable) {
<div class="toggler"></div>
}
<span class="segment-key">{{ segment.key }}</span>
<section class="segment-main">
<span
[class.expandable]="expandable"
[class.expanded]="segment.expanded"
(click)="toggle(segment)">
@if (expandable) {
<div class="toggler"></div>
}
<span class="segment-key">{{ segment.key }}</span>
</span>
<span class="puctuation">: </span>
@if (!expandable || !segment.expanded) {
<span [class]="expandable ? 'segment-label' : 'segment-value'">{{
segment.description
}}</span>
<span
[class]="expandable ? 'segment-label' : 'segment-value'"
[class.clickable]="enableClickableValues()"
(click)="onValueClickHandler(segment)">
{{ segment.description }}
</span>
} @else if (expandable && segment.expanded) {
<span class="puctuation">
@if (segment.type === 'array') {
Expand All @@ -37,7 +39,10 @@
[json]="segment.value"
[expanded]="expanded()"
[depth]="depth()"
[_currentDepth]="_currentDepth() + 1" />
[enableClickableValues]="enableClickableValues()"
[_parent]="segment"
[_currentDepth]="_currentDepth() + 1"
(onValueClick)="onValueClickHandler($event)" />
@if (['object', 'array'].includes(segment.type ?? '')) {
<span class="puctuation">
@if (segment.type === 'array') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ $type-colors: (
cursor: pointer;
}

.clickable {
cursor: pointer;
text-decoration: none;

&:hover {
text-decoration: underline;
}
}

.puctuation {
color: var(--ngx-json-punctuation, #000);
}
Expand Down
19 changes: 17 additions & 2 deletions projects/ngx-json-treeview/src/lib/ngx-json-treeview.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { CommonModule } from '@angular/common';
import { Component, computed, input } from '@angular/core';
import { Component, computed, input, output } from '@angular/core';
import { decycle, previewString } from './util';

export interface Segment {
key: string;
value: any;
type: undefined | string;
type?: string;
description: string;
expanded: boolean;
parent?: Segment;
path: string;
}

@Component({
Expand All @@ -21,8 +23,13 @@ export class NgxJsonTreeviewComponent {
json = input.required<any>();
expanded = input<boolean>(true);
depth = input<number>(-1);
enableClickableValues = input<boolean>(false);

_parent = input<Segment>();
_currentDepth = input<number>(0);

onValueClick = output<Segment>();

// computed values
segments = computed<Segment[]>(() => {
const json = decycle(this.json());
Expand Down Expand Up @@ -55,8 +62,16 @@ export class NgxJsonTreeviewComponent {
}
}

onValueClickHandler(segment: Segment) {
if (this.enableClickableValues()) {
this.onValueClick.emit(segment);
}
}

private parseKeyValue(key: any, value: any): Segment {
const segment: Segment = {
parent: this._parent(),
path: this._parent() ? `${this._parent()!.path}.${key}` : key,
key: key,
value: value,
type: undefined,
Expand Down