• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 20960087204

13 Jan 2026 02:19PM UTC coverage: 12.713% (-78.8%) from 91.5%
20960087204

Pull #16746

github

web-flow
Merge 9afce6e5d into a967f087e
Pull Request #16746: fix(csv): export summaries - master

1008 of 16803 branches covered (6.0%)

19 of 23 new or added lines in 2 files covered. (82.61%)

24693 existing lines in 336 files now uncovered.

3985 of 31345 relevant lines covered (12.71%)

2.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.06
/projects/igniteui-angular/tree/src/tree/tree.component.ts
1
import { Component, QueryList, Input, Output, EventEmitter, ContentChild, Directive, TemplateRef, OnInit, AfterViewInit, ContentChildren, OnDestroy, HostBinding, ElementRef, booleanAttribute, inject } from '@angular/core';
2

3
import { Subject } from 'rxjs';
4
import { takeUntil, throttleTime } from 'rxjs/operators';
5

6
import {
7
    IGX_TREE_COMPONENT, IgxTreeSelectionType, IgxTree, ITreeNodeToggledEventArgs,
8
    ITreeNodeTogglingEventArgs, ITreeNodeSelectionEvent, IgxTreeNode, IgxTreeSearchResolver
9
} from './common';
10
import { IgxTreeNavigationService } from './tree-navigation.service';
11
import { IgxTreeNodeComponent } from './tree-node/tree-node.component';
12
import { IgxTreeSelectionService } from './tree-selection.service';
13
import { IgxTreeService } from './tree.service';
14
import { growVerIn, growVerOut } from 'igniteui-angular/animations';
15
import { PlatformUtil, resizeObservable } from 'igniteui-angular/core';
16
import { ToggleAnimationSettings } from 'igniteui-angular/expansion-panel';
17

18
/**
19
 * @hidden @internal
20
 * Used for templating the select marker of the tree
21
 */
22
@Directive({
23
    selector: '[igxTreeSelectMarker]',
24
    standalone: true
25
})
26
export class IgxTreeSelectMarkerDirective {
3✔
27
}
28

29
/**
30
 * @hidden @internal
31
 * Used for templating the expand indicator of the tree
32
 */
33
@Directive({
34
    selector: '[igxTreeExpandIndicator]',
35
    standalone: true
36
})
37
export class IgxTreeExpandIndicatorDirective {
3✔
38
}
39

40
/**
41
 * IgxTreeComponent allows a developer to show a set of nodes in a hierarchical fashion.
42
 *
43
 * @igxModule IgxTreeModule
44
 * @igxKeywords tree
45
 * @igxTheme igx-tree-theme
46
 * @igxGroup Grids & Lists
47
 *
48
 * @remark
49
 * The Angular Tree Component allows users to represent hierarchical data in a tree-view structure,
50
 * maintaining parent-child relationships, as well as to define static tree-view structure without a corresponding data model.
51
 * Its primary purpose is to allow end-users to visualize and navigate within hierarchical data structures.
52
 * The Ignite UI for Angular Tree Component also provides load on demand capabilities, item activation,
53
 * bi-state and cascading selection of items through built-in checkboxes, built-in keyboard navigation and more.
54
 *
55
 * @example
56
 * ```html
57
 * <igx-tree>
58
 *   <igx-tree-node>
59
 *      I am a parent node 1
60
 *      <igx-tree-node>
61
 *          I am a child node 1
62
 *      </igx-tree-node>
63
 *      ...
64
 *   </igx-tree-node>
65
 *         ...
66
 * </igx-tree>
67
 * ```
68
 */
69
@Component({
70
    selector: 'igx-tree',
71
    templateUrl: 'tree.component.html',
72
    providers: [
73
        IgxTreeService,
74
        IgxTreeSelectionService,
75
        IgxTreeNavigationService,
76
        { provide: IGX_TREE_COMPONENT, useExisting: IgxTreeComponent },
77
    ],
78
    standalone: true
79
})
80
export class IgxTreeComponent implements IgxTree, OnInit, AfterViewInit, OnDestroy {
3✔
UNCOV
81
    private navService = inject(IgxTreeNavigationService);
×
UNCOV
82
    private selectionService = inject(IgxTreeSelectionService);
×
UNCOV
83
    private treeService = inject(IgxTreeService);
×
UNCOV
84
    private element = inject<ElementRef<HTMLElement>>(ElementRef);
×
UNCOV
85
    private platform = inject(PlatformUtil);
×
86

87

88
    @HostBinding('class.igx-tree')
UNCOV
89
    public cssClass = 'igx-tree';
×
90

91
    /**
92
     * Gets/Sets tree selection mode
93
     *
94
     * @remarks
95
     * By default the tree selection mode is 'None'
96
     * @param selectionMode: IgxTreeSelectionType
97
     */
98
    @Input()
99
    public get selection() {
UNCOV
100
        return this._selection;
×
101
    }
102

103
    public set selection(selectionMode: IgxTreeSelectionType) {
UNCOV
104
        this._selection = selectionMode;
×
UNCOV
105
        this.selectionService.clearNodesSelection();
×
106
    }
107

108
    /** Get/Set how the tree should handle branch expansion.
109
     * If set to `true`, only a single branch can be expanded at a time, collapsing all others
110
     *
111
     * ```html
112
     * <igx-tree [singleBranchExpand]="true">
113
     * ...
114
     * </igx-tree>
115
     * ```
116
     *
117
     * ```typescript
118
     * const tree: IgxTree = this.tree;
119
     * this.tree.singleBranchExpand = false;
120
     * ```
121
     */
122
    @Input({ transform: booleanAttribute })
UNCOV
123
    public singleBranchExpand = false;
×
124

125
    /** Get/Set if nodes should be expanded/collapsed when clicking over them.
126
     *
127
     * ```html
128
     * <igx-tree [toggleNodeOnClick]="true">
129
     * ...
130
     * </igx-tree>
131
     * ```
132
     *
133
     * ```typescript
134
     * const tree: IgxTree = this.tree;
135
     * this.tree.toggleNodeOnClick = false;
136
     * ```
137
     */
138
    @Input({ transform: booleanAttribute })
UNCOV
139
    public toggleNodeOnClick = false;
×
140

141

142
    /** Get/Set the animation settings that branches should use when expanding/collpasing.
143
     *
144
     * ```html
145
     * <igx-tree [animationSettings]="customAnimationSettings">
146
     * </igx-tree>
147
     * ```
148
     *
149
     * ```typescript
150
     * const animationSettings: ToggleAnimationSettings = {
151
     *      openAnimation: growVerIn,
152
     *      closeAnimation: growVerOut
153
     * };
154
     *
155
     * this.tree.animationSettings = animationSettings;
156
     * ```
157
     */
158
    @Input()
UNCOV
159
    public animationSettings: ToggleAnimationSettings = {
×
160
        openAnimation: growVerIn,
161
        closeAnimation: growVerOut
162
    };
163

164
    /** Emitted when the node selection is changed through interaction
165
     *
166
     * ```html
167
     * <igx-tree (nodeSelection)="handleNodeSelection($event)">
168
     * </igx-tree>
169
     * ```
170
     *
171
     *```typescript
172
     * public handleNodeSelection(event: ITreeNodeSelectionEvent) {
173
     *  const newSelection: IgxTreeNode<any>[] = event.newSelection;
174
     *  const added: IgxTreeNode<any>[] = event.added;
175
     *  console.log("New selection will be: ", newSelection);
176
     *  console.log("Added nodes: ", event.added);
177
     * }
178
     *```
179
     */
180
    @Output()
UNCOV
181
    public nodeSelection = new EventEmitter<ITreeNodeSelectionEvent>();
×
182

183
    /** Emitted when a node is expanding, before it finishes
184
     *
185
     * ```html
186
     * <igx-tree (nodeExpanding)="handleNodeExpanding($event)">
187
     * </igx-tree>
188
     * ```
189
     *
190
     *```typescript
191
     * public handleNodeExpanding(event: ITreeNodeTogglingEventArgs) {
192
     *  const expandedNode: IgxTreeNode<any> = event.node;
193
     *  if (expandedNode.disabled) {
194
     *      event.cancel = true;
195
     *  }
196
     * }
197
     *```
198
     */
199
    @Output()
UNCOV
200
    public nodeExpanding = new EventEmitter<ITreeNodeTogglingEventArgs>();
×
201

202
    /** Emitted when a node is expanded, after it finishes
203
     *
204
     * ```html
205
     * <igx-tree (nodeExpanded)="handleNodeExpanded($event)">
206
     * </igx-tree>
207
     * ```
208
     *
209
     *```typescript
210
     * public handleNodeExpanded(event: ITreeNodeToggledEventArgs) {
211
     *  const expandedNode: IgxTreeNode<any> = event.node;
212
     *  console.log("Node is expanded: ", expandedNode.data);
213
     * }
214
     *```
215
     */
216
    @Output()
UNCOV
217
    public nodeExpanded = new EventEmitter<ITreeNodeToggledEventArgs>();
×
218

219
    /** Emitted when a node is collapsing, before it finishes
220
     *
221
     * ```html
222
     * <igx-tree (nodeCollapsing)="handleNodeCollapsing($event)">
223
     * </igx-tree>
224
     * ```
225
     *
226
     *```typescript
227
     * public handleNodeCollapsing(event: ITreeNodeTogglingEventArgs) {
228
     *  const collapsedNode: IgxTreeNode<any> = event.node;
229
     *  if (collapsedNode.alwaysOpen) {
230
     *      event.cancel = true;
231
     *  }
232
     * }
233
     *```
234
     */
235
    @Output()
UNCOV
236
    public nodeCollapsing = new EventEmitter<ITreeNodeTogglingEventArgs>();
×
237

238
    /** Emitted when a node is collapsed, after it finishes
239
     *
240
     * @example
241
     * ```html
242
     * <igx-tree (nodeCollapsed)="handleNodeCollapsed($event)">
243
     * </igx-tree>
244
     * ```
245
     * ```typescript
246
     * public handleNodeCollapsed(event: ITreeNodeToggledEventArgs) {
247
     *  const collapsedNode: IgxTreeNode<any> = event.node;
248
     *  console.log("Node is collapsed: ", collapsedNode.data);
249
     * }
250
     * ```
251
     */
252
    @Output()
UNCOV
253
    public nodeCollapsed = new EventEmitter<ITreeNodeToggledEventArgs>();
×
254

255
    /**
256
     * Emitted when the active node is changed.
257
     *
258
     * @example
259
     * ```
260
     * <igx-tree (activeNodeChanged)="activeNodeChanged($event)"></igx-tree>
261
     * ```
262
     */
263
    @Output()
UNCOV
264
    public activeNodeChanged = new EventEmitter<IgxTreeNode<any>>();
×
265

266
    /**
267
     * A custom template to be used for the expand indicator of nodes
268
     * ```html
269
     * <igx-tree>
270
     *  <ng-template igxTreeExpandIndicator let-expanded>
271
     *      <igx-icon>{{ expanded ? "close_fullscreen": "open_in_full"}}</igx-icon>
272
     *  </ng-template>
273
     * </igx-tree>
274
     * ```
275
     */
276
    @ContentChild(IgxTreeExpandIndicatorDirective, { read: TemplateRef })
277
    public expandIndicator: TemplateRef<any>;
278

279
    /** @hidden @internal */
280
    @ContentChildren(IgxTreeNodeComponent, { descendants: true })
281
    public nodes: QueryList<IgxTreeNodeComponent<any>>;
282

283
    /** @hidden @internal */
UNCOV
284
    public disabledChange = new EventEmitter<IgxTreeNode<any>>();
×
285

286
    /**
287
     * Returns all **root level** nodes
288
     *
289
     * ```typescript
290
     * const tree: IgxTree = this.tree;
291
     * const rootNodes: IgxTreeNodeComponent<any>[] = tree.rootNodes;
292
     * ```
293
     */
294
    public get rootNodes(): IgxTreeNodeComponent<any>[] {
UNCOV
295
        return this.nodes?.filter(node => node.level === 0);
×
296
    }
297

298
    /**
299
     * Emitted when the active node is set through API
300
     *
301
     * @hidden @internal
302
     */
UNCOV
303
    public activeNodeBindingChange = new EventEmitter<IgxTreeNode<any>>();
×
304

305
    /** @hidden @internal */
UNCOV
306
    public forceSelect = [];
×
307

308
    /** @hidden @internal */
UNCOV
309
    public resizeNotify = new Subject<void>();
×
310

UNCOV
311
    private _selection: IgxTreeSelectionType = IgxTreeSelectionType.None;
×
UNCOV
312
    private destroy$ = new Subject<void>();
×
UNCOV
313
    private unsubChildren$ = new Subject<void>();
×
314

315
    constructor() {
UNCOV
316
        this.selectionService.register(this);
×
UNCOV
317
        this.treeService.register(this);
×
UNCOV
318
        this.navService.register(this);
×
319
    }
320

321
    /** @hidden @internal */
322
    public get nativeElement() {
UNCOV
323
        return this.element.nativeElement;
×
324
    }
325

326
    /**
327
     * Expands all of the passed nodes.
328
     * If no nodes are passed, expands ALL nodes
329
     *
330
     * @param nodes nodes to be expanded
331
     *
332
     * ```typescript
333
     * const targetNodes: IgxTreeNode<any> = this.tree.findNodes(true, (_data: any, node: IgxTreeNode<any>) => node.data.expandable);
334
     * tree.expandAll(nodes);
335
     * ```
336
     */
337
    public expandAll(nodes?: IgxTreeNode<any>[]) {
UNCOV
338
        nodes = nodes || this.nodes.toArray();
×
UNCOV
339
        nodes.forEach(e => e.expanded = true);
×
340
    }
341

342
    /**
343
     * Collapses all of the passed nodes.
344
     * If no nodes are passed, collapses ALL nodes
345
     *
346
     * @param nodes nodes to be collapsed
347
     *
348
     * ```typescript
349
     * const targetNodes: IgxTreeNode<any> = this.tree.findNodes(true, (_data: any, node: IgxTreeNode<any>) => node.data.collapsible);
350
     * tree.collapseAll(nodes);
351
     * ```
352
     */
353
    public collapseAll(nodes?: IgxTreeNode<any>[]) {
UNCOV
354
        nodes = nodes || this.nodes.toArray();
×
UNCOV
355
        nodes.forEach(e => e.expanded = false);
×
356
    }
357

358
    /**
359
     * Deselect all nodes if the nodes collection is empty. Otherwise, deselect the nodes in the nodes collection.
360
     *
361
     * @example
362
     * ```typescript
363
     *  const arr = [
364
     *      this.tree.nodes.toArray()[0],
365
     *      this.tree.nodes.toArray()[1]
366
     *  ];
367
     *  this.tree.deselectAll(arr);
368
     * ```
369
     * @param nodes: IgxTreeNodeComponent<any>[]
370
     */
371
    public deselectAll(nodes?: IgxTreeNodeComponent<any>[]) {
UNCOV
372
        this.selectionService.deselectNodesWithNoEvent(nodes);
×
373
    }
374

375
    /**
376
     * Returns all of the nodes that match the passed searchTerm.
377
     * Accepts a custom comparer function for evaluating the search term against the nodes.
378
     *
379
     * @remarks
380
     * Default search compares the passed `searchTerm` against the node's `data` Input.
381
     * When using `findNodes` w/o a `comparer`, make sure all nodes have `data` passed.
382
     *
383
     * @param searchTerm The data of the searched node
384
     * @param comparer A custom comparer function that evaluates the passed `searchTerm` against all nodes.
385
     * @returns Array of nodes that match the search. `null` if no nodes are found.
386
     *
387
     * ```html
388
     * <igx-tree>
389
     *     <igx-tree-node *ngFor="let node of data" [data]="node">
390
     *          {{ node.label }}
391
     *     </igx-tree-node>
392
     * </igx-tree>
393
     * ```
394
     *
395
     * ```typescript
396
     * public data: DataEntry[] = FETCHED_DATA;
397
     * ...
398
     * const matchedNodes: IgxTreeNode<DataEntry>[] = this.tree.findNodes<DataEntry>(searchTerm: data[5]);
399
     * ```
400
     *
401
     * Using a custom comparer
402
     * ```typescript
403
     * public data: DataEntry[] = FETCHED_DATA;
404
     * ...
405
     * const comparer: IgxTreeSearchResolver = (data: any, node: IgxTreeNode<DataEntry>) {
406
     *      return node.data.index % 2 === 0;
407
     * }
408
     * const evenIndexNodes: IgxTreeNode<DataEntry>[] = this.tree.findNodes<DataEntry>(null, comparer);
409
     * ```
410
     */
411
    public findNodes(searchTerm: any, comparer?: IgxTreeSearchResolver): IgxTreeNodeComponent<any>[] | null {
UNCOV
412
        const compareFunc = comparer || this._comparer;
×
UNCOV
413
        const results = this.nodes.filter(node => compareFunc(searchTerm, node));
×
UNCOV
414
        return results?.length === 0 ? null : results;
×
415
    }
416

417
    /** @hidden @internal */
418
    public handleKeydown(event: KeyboardEvent) {
UNCOV
419
        this.navService.handleKeydown(event);
×
420
    }
421

422
    /** @hidden @internal */
423
    public ngOnInit() {
UNCOV
424
        this.disabledChange.pipe(takeUntil(this.destroy$)).subscribe((e) => {
×
UNCOV
425
            this.navService.update_disabled_cache(e);
×
426
        });
UNCOV
427
        this.activeNodeBindingChange.pipe(takeUntil(this.destroy$)).subscribe((node) => {
×
UNCOV
428
            this.expandToNode(this.navService.activeNode);
×
UNCOV
429
            this.scrollNodeIntoView(node?.header?.nativeElement);
×
430
        });
UNCOV
431
        this.subToCollapsing();
×
UNCOV
432
        this.resizeNotify.pipe(
×
433
            throttleTime(40, null, { trailing: true }),
434
            takeUntil(this.destroy$)
435
        )
436
        .subscribe(() => {
UNCOV
437
            requestAnimationFrame(() => {
×
UNCOV
438
                this.scrollNodeIntoView(this.navService.activeNode?.header.nativeElement);
×
439
            });
440
        });
441
    }
442

443
    /** @hidden @internal */
444
    public ngAfterViewInit() {
UNCOV
445
        this.nodes.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
446
            this.subToChanges();
×
447
        });
UNCOV
448
        this.scrollNodeIntoView(this.navService.activeNode?.header?.nativeElement);
×
UNCOV
449
        this.subToChanges();
×
UNCOV
450
        resizeObservable(this.nativeElement).pipe(takeUntil(this.destroy$)).subscribe(() => this.resizeNotify.next());
×
451
    }
452

453
    /** @hidden @internal */
454
    public ngOnDestroy() {
UNCOV
455
        this.unsubChildren$.next();
×
UNCOV
456
        this.unsubChildren$.complete();
×
UNCOV
457
        this.destroy$.next();
×
UNCOV
458
        this.destroy$.complete();
×
459
    }
460

461
    private expandToNode(node: IgxTreeNode<any>) {
UNCOV
462
        if (node && node.parentNode) {
×
UNCOV
463
            node.path.forEach(n => {
×
UNCOV
464
                if (n !== node && !n.expanded) {
×
465
                    n.expanded = true;
×
466
                }
467
            });
468
        }
469
    }
470

471
    private subToCollapsing() {
UNCOV
472
        this.nodeCollapsing.pipe(takeUntil(this.destroy$)).subscribe(event => {
×
UNCOV
473
            if (event.cancel) {
×
474
                return;
×
475
            }
UNCOV
476
            this.navService.update_visible_cache(event.node, false);
×
477
        });
UNCOV
478
        this.nodeExpanding.pipe(takeUntil(this.destroy$)).subscribe(event => {
×
UNCOV
479
            if (event.cancel) {
×
480
                return;
×
481
            }
UNCOV
482
            this.navService.update_visible_cache(event.node, true);
×
483
        });
484
    }
485

486
    private subToChanges() {
UNCOV
487
        this.unsubChildren$.next();
×
UNCOV
488
        const toBeSelected = [...this.forceSelect];
×
UNCOV
489
        if(this.platform.isBrowser) {
×
UNCOV
490
            requestAnimationFrame(() => {
×
UNCOV
491
                this.selectionService.selectNodesWithNoEvent(toBeSelected);
×
492
            });
493
        }
UNCOV
494
        this.forceSelect = [];
×
UNCOV
495
        this.nodes.forEach(node => {
×
UNCOV
496
            node.expandedChange.pipe(takeUntil(this.unsubChildren$)).subscribe(nodeState => {
×
UNCOV
497
                this.navService.update_visible_cache(node, nodeState);
×
498
            });
UNCOV
499
            node.closeAnimationDone.pipe(takeUntil(this.unsubChildren$)).subscribe(() => {
×
UNCOV
500
                const targetElement = this.navService.focusedNode?.header.nativeElement;
×
UNCOV
501
                this.scrollNodeIntoView(targetElement);
×
502
            });
UNCOV
503
            node.openAnimationDone.pipe(takeUntil(this.unsubChildren$)).subscribe(() => {
×
UNCOV
504
                const targetElement = this.navService.focusedNode?.header.nativeElement;
×
UNCOV
505
                this.scrollNodeIntoView(targetElement);
×
506
            });
507
        });
UNCOV
508
        this.navService.init_invisible_cache();
×
509
    }
510

511
    private scrollNodeIntoView(el: HTMLElement) {
UNCOV
512
        if (!el) {
×
UNCOV
513
            return;
×
514
        }
UNCOV
515
        const nodeRect = el.getBoundingClientRect();
×
UNCOV
516
        const treeRect = this.nativeElement.getBoundingClientRect();
×
UNCOV
517
        const topOffset = treeRect.top > nodeRect.top ? nodeRect.top - treeRect.top : 0;
×
UNCOV
518
        const bottomOffset = treeRect.bottom < nodeRect.bottom ? nodeRect.bottom - treeRect.bottom : 0;
×
UNCOV
519
        const shouldScroll = !!topOffset || !!bottomOffset;
×
UNCOV
520
        if (shouldScroll && this.nativeElement.scrollHeight > this.nativeElement.clientHeight) {
×
521
            // this.nativeElement.scrollTop = nodeRect.y - treeRect.y - nodeRect.height;
UNCOV
522
            this.nativeElement.scrollTop =
×
523
                this.nativeElement.scrollTop + bottomOffset + topOffset + (topOffset ? -1 : +1) * nodeRect.height;
×
524
        }
525
    }
526

UNCOV
527
    private _comparer = <T>(data: T, node: IgxTreeNodeComponent<T>) => node.data === data;
×
528

529
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc