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

IgniteUI / igniteui-angular / 7061338687

01 Dec 2023 02:32PM UTC coverage: 91.844% (+0.002%) from 91.842%
7061338687

push

github

web-flow
Merge pull request #13707 from IgniteUI/thristodorova/feat-13491

feat(tree): add toggleNodeOnClick property to the tree

12500 of 14565 branches covered (0.0%)

6 of 8 new or added lines in 2 files covered. (75.0%)

1 existing line in 1 file now uncovered.

25484 of 27747 relevant lines covered (91.84%)

30465.24 hits per line

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

92.91
/projects/igniteui-angular/src/lib/tree/tree-node/tree-node.component.ts
1
import {
2
    ChangeDetectorRef,
3
    Component,
4
    ContentChildren,
5
    Directive,
6
    ElementRef,
7
    EventEmitter,
8
    HostBinding,
9
    HostListener,
10
    Inject,
11
    Input,
12
    OnDestroy,
13
    OnInit,
14
    Optional,
15
    Output,
16
    QueryList,
17
    SkipSelf,
18
    TemplateRef,
19
    ViewChild,
20
    booleanAttribute
21
} from '@angular/core';
22
import { takeUntil } from 'rxjs/operators';
23
import { DisplayDensity } from '../../core/density';
24
import { ITreeResourceStrings, TreeResourceStringsEN } from '../../core/i18n/tree-resources';
25
import { ToggleAnimationPlayer, ToggleAnimationSettings } from '../../expansion-panel/toggle-animation-component';
26
import { IgxAngularAnimationService } from '../../services/animation/angular-animation-service';
27
import { AnimationService } from '../../services/animation/animation';
28
import {
29
    IgxTree,
30
    IgxTreeNode,
31
    IgxTreeSelectionType,
32
    IGX_TREE_COMPONENT,
33
    IGX_TREE_NODE_COMPONENT,
34
    ITreeNodeTogglingEventArgs
35
} from '../common';
36
import { IgxTreeNavigationService } from '../tree-navigation.service';
37
import { IgxTreeSelectionService } from '../tree-selection.service';
38
import { IgxTreeService } from '../tree.service';
39
import { IgxCircularProgressBarComponent } from '../../progressbar/progressbar.component';
40
import { IgxCheckboxComponent } from '../../checkbox/checkbox.component';
41
import { IgxIconComponent } from '../../icon/icon.component';
42
import { NgTemplateOutlet, NgIf, NgClass, NgFor } from '@angular/common';
43
import { getCurrentResourceStrings } from '../../core/i18n/resources';
44

45
// TODO: Implement aria functionality
46
/**
47
 * @hidden @internal
48
 * Used for links (`a` tags) in the body of an `igx-tree-node`. Handles aria and event dispatch.
49
 */
50
@Directive({
51
    selector: `[igxTreeNodeLink]`,
52
    standalone: true
53
})
54
export class IgxTreeNodeLinkDirective implements OnDestroy {
2✔
55

56
    @HostBinding('attr.role')
57
    public role = 'treeitem';
30✔
58

59
    /**
60
     * The node's parent. Should be used only when the link is defined
61
     * in `<ng-template>` tag outside of its parent, as Angular DI will not properly provide a reference
62
     *
63
     * ```html
64
     * <igx-tree>
65
     *     <igx-tree-node #myNode *ngFor="let node of data" [data]="node">
66
     *         <ng-template *ngTemplateOutlet="nodeTemplate; context: { $implicit: data, parentNode: myNode }">
67
     *         </ng-template>
68
     *     </igx-tree-node>
69
     *     ...
70
     *     <!-- node template is defined under tree to access related services -->
71
     *     <ng-template #nodeTemplate let-data let-node="parentNode">
72
     *         <a [igxTreeNodeLink]="node">{{ data.label }}</a>
73
     *     </ng-template>
74
     * </igx-tree>
75
     * ```
76
     */
77
    @Input('igxTreeNodeLink')
78
    public set parentNode(val: any) {
79
        if (val) {
30✔
80
            this._parentNode = val;
15✔
81
            (this._parentNode as any).addLinkChild(this);
15✔
82
        }
83
    }
84

85
    public get parentNode(): any {
86
        return this._parentNode;
113✔
87
    }
88

89
    /** A pointer to the parent node */
90
    private get target(): IgxTreeNode<any> {
91
        return this.node || this.parentNode;
226✔
92
    }
93

94
    private _parentNode: IgxTreeNode<any> = null;
30✔
95

96
    constructor(@Optional() @Inject(IGX_TREE_NODE_COMPONENT)
97
    private node: IgxTreeNode<any>,
30✔
98
        private navService: IgxTreeNavigationService,
30✔
99
        public elementRef: ElementRef) {
30✔
100
    }
101

102
    /** @hidden @internal */
103
    @HostBinding('attr.tabindex')
104
    public get tabIndex(): number {
105
        return this.navService.focusedNode === this.target ? (this.target?.disabled ? -1 : 0) : -1;
181!
106
    }
107

108
    /**
109
     * @hidden @internal
110
     * Clear the node's focused state
111
     */
112
    @HostListener('blur')
113
    public handleBlur() {
114
        this.target.isFocused = false;
×
115
    }
116

117
    /**
118
     * @hidden @internal
119
     * Set the node as focused
120
     */
121
    @HostListener('focus')
122
    public handleFocus() {
123
        if (this.target && !this.target.disabled) {
2✔
124
            if (this.navService.focusedNode !== this.target) {
2!
125
                this.navService.focusedNode = this.target;
×
126
            }
127
            this.target.isFocused = true;
2✔
128
        }
129
    }
130

131
    public ngOnDestroy() {
132
        this.target.removeLinkChild(this);
30✔
133
    }
134
}
135

136
/**
137
 *
138
 * The tree node component represents a child node of the tree component or another tree node.
139
 * Usage:
140
 *
141
 * ```html
142
 *  <igx-tree>
143
 *  ...
144
 *    <igx-tree-node [data]="data" [selected]="service.isNodeSelected(data.Key)" [expanded]="service.isNodeExpanded(data.Key)">
145
 *      {{ data.FirstName }} {{ data.LastName }}
146
 *    </igx-tree-node>
147
 *  ...
148
 *  </igx-tree>
149
 * ```
150
 */
151
@Component({
152
    selector: 'igx-tree-node',
153
    templateUrl: 'tree-node.component.html',
154
    providers: [
155
        { provide: IGX_TREE_NODE_COMPONENT, useExisting: IgxTreeNodeComponent }
156
    ],
157
    standalone: true,
158
    imports: [NgTemplateOutlet, NgIf, IgxIconComponent, IgxCheckboxComponent, NgClass, NgFor, IgxCircularProgressBarComponent]
159
})
160
export class IgxTreeNodeComponent<T> extends ToggleAnimationPlayer implements IgxTreeNode<T>, OnInit, OnDestroy {
2✔
161
    /**
162
     * The data entry that the node is visualizing.
163
     *
164
     * @remarks
165
     * Required for searching through nodes.
166
     *
167
     * @example
168
     * ```html
169
     *  <igx-tree>
170
     *  ...
171
     *    <igx-tree-node [data]="data">
172
     *      {{ data.FirstName }} {{ data.LastName }}
173
     *    </igx-tree-node>
174
     *  ...
175
     *  </igx-tree>
176
     * ```
177
     */
178
    @Input()
179
    public data: T;
180

181
    /**
182
     * To be used for load-on-demand scenarios in order to specify whether the node is loading data.
183
     *
184
     * @remarks
185
     * Loading nodes do not render children.
186
     */
187
    @Input({ transform: booleanAttribute })
188
    public loading = false;
3,640✔
189

190
    // TO DO: return different tab index depending on anchor child
191
    /** @hidden @internal */
192
    public set tabIndex(val: number) {
193
        this._tabIndex = val;
73✔
194
    }
195

196
    /** @hidden @internal */
197
    public get tabIndex(): number {
198
        if (this.disabled) {
38,433✔
199
            return -1;
736✔
200
        }
201
        if (this._tabIndex === null) {
37,697✔
202
            if (this.navService.focusedNode === null) {
37,369✔
203
                return this.hasLinkChildren ? -1 : 0;
32,858✔
204
            }
205
            return -1;
4,511✔
206
        }
207
        return this.hasLinkChildren ? -1 : this._tabIndex;
328✔
208
    }
209

210
    /** @hidden @internal */
211
    public override get animationSettings(): ToggleAnimationSettings {
212
        return this.tree.animationSettings;
25✔
213
    }
214

215
    /**
216
     * Gets/Sets the resource strings.
217
     *
218
     * @remarks
219
     * Uses EN resources by default.
220
     */
221
    @Input()
222
    public set resourceStrings(value: ITreeResourceStrings) {
223
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
224
    }
225

226
    /**
227
     * An accessor that returns the resource strings.
228
     */
229
    public get resourceStrings(): ITreeResourceStrings {
230
        return this._resourceStrings;
22,071✔
231
    }
232

233
    /**
234
     * Gets/Sets the active state of the node
235
     *
236
     * @param value: boolean
237
     */
238
    @Input({ transform: booleanAttribute })
239
    public set active(value: boolean) {
240
        if (value) {
247✔
241
            this.navService.activeNode = this;
175✔
242
            this.tree.activeNodeBindingChange.emit(this);
175✔
243
        }
244
    }
245

246
    public get active(): boolean {
247
        return this.navService.activeNode === this;
38,434✔
248
    }
249

250
    /**
251
     * Emitted when the node's `selected` property changes.
252
     *
253
     * ```html
254
     * <igx-tree>
255
     *      <igx-tree-node *ngFor="let node of data" [data]="node" [(selected)]="node.selected">
256
     *      </igx-tree-node>
257
     * </igx-tree>
258
     * ```
259
     *
260
     * ```typescript
261
     * const node: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
262
     * node.selectedChange.pipe(takeUntil(this.destroy$)).subscribe((e: boolean) => console.log("Node selection changed to ", e))
263
     * ```
264
     */
265
    @Output()
266
    public selectedChange = new EventEmitter<boolean>();
3,640✔
267

268
    /**
269
     * Emitted when the node's `expanded` property changes.
270
     *
271
     * ```html
272
     * <igx-tree>
273
     *      <igx-tree-node *ngFor="let node of data" [data]="node" [(expanded)]="node.expanded">
274
     *      </igx-tree-node>
275
     * </igx-tree>
276
     * ```
277
     *
278
     * ```typescript
279
     * const node: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
280
     * node.expandedChange.pipe(takeUntil(this.destroy$)).subscribe((e: boolean) => console.log("Node expansion state changed to ", e))
281
     * ```
282
     */
283
    @Output()
284
    public expandedChange = new EventEmitter<boolean>();
3,640✔
285

286
    /** @hidden @internal */
287
    public get focused() {
288
        return this.isFocused &&
38,433✔
289
            this.navService.focusedNode === this;
290
    }
291

292
    /**
293
     * Retrieves the full path to the node incuding itself
294
     *
295
     * ```typescript
296
     * const node: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
297
     * const path: IgxTreeNode<any>[] = node.path;
298
     * ```
299
     */
300
    public get path(): IgxTreeNode<any>[] {
301
        return this.parentNode?.path ? [...this.parentNode.path, this] : [this];
774✔
302
    }
303

304
    // TODO: bind to disabled state when node is dragged
305
    /**
306
     * Gets/Sets the disabled state of the node
307
     *
308
     * @param value: boolean
309
     */
310
    @Input({ transform: booleanAttribute })
311
    @HostBinding('class.igx-tree-node--disabled')
312
    public get disabled(): boolean {
313
        return this._disabled;
115,439✔
314
    }
315

316
    public set disabled(value: boolean) {
317
        if (value !== this._disabled) {
398✔
318
            this._disabled = value;
83✔
319
            this.tree.disabledChange.emit(this);
83✔
320
        }
321
    }
322

323
    /** @hidden @internal */
324
    @HostBinding('class.igx-tree-node')
325
    public cssClass = 'igx-tree-node';
3,640✔
326

327
    @HostBinding('style.--component-size')
328
    public get size(): string {
329
        switch (this.tree.displayDensity) {
38,410!
330
            case DisplayDensity.compact:
331
                return 'var(--ig-size, var(--ig-size-small))';
×
332
            case DisplayDensity.cosy:
333
                return 'var(--ig-size, var(--ig-size-medium))';
×
334
            case DisplayDensity.comfortable:
335
            default:
336
                return 'var(--ig-size, var(--ig-size-large))';
38,410✔
337
        }
338
    }
339

340
    /** @hidden @internal */
341
    @HostBinding('attr.role')
342
    public get role() {
343
        return this.hasLinkChildren ? 'none' : 'treeitem';
76,843✔
344
    }
345

346
    /** @hidden @internal */
347
    @ContentChildren(IgxTreeNodeLinkDirective, { read: ElementRef })
348
    public linkChildren: QueryList<ElementRef>;
349

350
    /** @hidden @internal */
351
    @ContentChildren(IGX_TREE_NODE_COMPONENT, { read: IGX_TREE_NODE_COMPONENT })
352
    public _children: QueryList<IgxTreeNode<any>>;
353

354
    /** @hidden @internal */
355
    @ContentChildren(IGX_TREE_NODE_COMPONENT, { read: IGX_TREE_NODE_COMPONENT, descendants: true })
356
    public allChildren: QueryList<IgxTreeNode<any>>;
357

358
    /**
359
     * Return the child nodes of the node (if any)
360
     *
361
     * @remark
362
     * Returns `null` if node does not have children
363
     *
364
     * @example
365
     * ```typescript
366
     * const node: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
367
     * const children: IgxTreeNode<any>[] = node.children;
368
     * ```
369
     */
370
    public get children(): IgxTreeNode<any>[] {
371
        return this._children?.length ? this._children.toArray() : null;
×
372
    }
373

374
    // TODO: will be used in Drag and Drop implementation
375
    /** @hidden @internal */
376
    @ViewChild('ghostTemplate', { read: ElementRef })
377
    public header: ElementRef;
378

379
    @ViewChild('defaultIndicator', { read: TemplateRef, static: true })
380
    private _defaultExpandIndicatorTemplate: TemplateRef<any>;
381

382
    @ViewChild('childrenContainer', { read: ElementRef })
383
    private childrenContainer: ElementRef;
384

385
    private get hasLinkChildren(): boolean {
386
        return this.linkChildren?.length > 0 || this.registeredChildren?.length > 0;
110,029✔
387
    }
388

389
    /** @hidden @internal */
390
    public get isCompact(): boolean {
391
        return this.tree?.displayDensity === DisplayDensity.compact;
3✔
392
    }
393

394
    /** @hidden @internal */
395
    public get isCosy(): boolean {
396
        return this.tree?.displayDensity === DisplayDensity.cosy;
3✔
397
    }
398

399
    /** @hidden @internal */
400
    public isFocused: boolean;
401

402
    /** @hidden @internal */
403
    public registeredChildren: IgxTreeNodeLinkDirective[] = [];
3,640✔
404

405
    /** @hidden @internal */
406
    private _resourceStrings = getCurrentResourceStrings(TreeResourceStringsEN);
3,640✔
407

408
    private _tabIndex = null;
3,640✔
409
    private _disabled = false;
3,640✔
410

411
    constructor(
412
        @Inject(IGX_TREE_COMPONENT) public tree: IgxTree,
3,640✔
413
        protected selectionService: IgxTreeSelectionService,
3,640✔
414
        protected treeService: IgxTreeService,
3,640✔
415
        protected navService: IgxTreeNavigationService,
3,640✔
416
        protected cdr: ChangeDetectorRef,
3,640✔
417
        @Inject(IgxAngularAnimationService) animationService: AnimationService,
418
        private element: ElementRef<HTMLElement>,
3,640✔
419
        @Optional() @SkipSelf() @Inject(IGX_TREE_NODE_COMPONENT) public parentNode: IgxTreeNode<any>
3,640✔
420
    ) {
421
        super(animationService);
3,640✔
422
    }
423

424
    /**
425
     * @hidden @internal
426
     */
427
    public get showSelectors() {
428
        return this.tree.selection !== IgxTreeSelectionType.None;
38,433✔
429
    }
430

431
    /**
432
     * @hidden @internal
433
     */
434
    public get indeterminate(): boolean {
435
        return this.selectionService.isNodeIndeterminate(this);
7,797✔
436
    }
437

438
    /** The depth of the node, relative to the root
439
     *
440
     * ```html
441
     * <igx-tree>
442
     *  ...
443
     *  <igx-tree-node #node>
444
     *      My level is {{ node.level }}
445
     *  </igx-tree-node>
446
     * </igx-tree>
447
     * ```
448
     *
449
     * ```typescript
450
     * const node: IgxTreeNode<any> = this.tree.findNodes(data[12])[0];
451
     * const level: number = node.level;
452
     * ```
453
     */
454
    public get level(): number {
455
        return this.parentNode ? this.parentNode.level + 1 : 0;
195,971✔
456
    }
457

458
    /** Get/set whether the node is selected. Supporst two-way binding.
459
     *
460
     * ```html
461
     * <igx-tree>
462
     *  ...
463
     *  <igx-tree-node *ngFor="let node of data" [(selected)]="node.selected">
464
     *      {{ node.label }}
465
     *  </igx-tree-node>
466
     * </igx-tree>
467
     * ```
468
     *
469
     * ```typescript
470
     * const node: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
471
     * const selected = node.selected;
472
     * node.selected = true;
473
     * ```
474
     */
475
    @Input({ transform: booleanAttribute })
476
    public get selected(): boolean {
477
        return this.selectionService.isNodeSelected(this);
46,954✔
478
    }
479

480
    public set selected(val: boolean) {
481
        if (!(this.tree?.nodes && this.tree.nodes.find((e) => e === this)) && val) {
6,843✔
482
            this.tree.forceSelect.push(this);
439✔
483
            return;
439✔
484
        }
485
        if (val && !this.selectionService.isNodeSelected(this)) {
1,011✔
486
            this.selectionService.selectNodesWithNoEvent([this]);
109✔
487
        }
488
        if (!val && this.selectionService.isNodeSelected(this)) {
1,011✔
489
            this.selectionService.deselectNodesWithNoEvent([this]);
9✔
490
        }
491
    }
492

493
    /** Get/set whether the node is expanded
494
     *
495
     * ```html
496
     * <igx-tree>
497
     *  ...
498
     *  <igx-tree-node *ngFor="let node of data" [expanded]="node.name === this.expandedNode">
499
     *      {{ node.label }}
500
     *  </igx-tree-node>
501
     * </igx-tree>
502
     * ```
503
     *
504
     * ```typescript
505
     * const node: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
506
     * const expanded = node.expanded;
507
     * node.expanded = true;
508
     * ```
509
     */
510
    @Input({ transform: booleanAttribute })
511
    public get expanded() {
512
        return this.treeService.isExpanded(this);
122,032✔
513
    }
514

515
    public set expanded(val: boolean) {
516
        if (val) {
1,629✔
517
            this.treeService.expand(this, false);
76✔
518
        } else {
519
            this.treeService.collapse(this);
1,553✔
520
        }
521
    }
522

523
    /** @hidden @internal */
524
    public get expandIndicatorTemplate(): TemplateRef<any> {
525
        return this.tree?.expandIndicator || this._defaultExpandIndicatorTemplate;
38,433✔
526
    }
527

528
    /**
529
     * The native DOM element representing the node. Could be null in certain environments.
530
     *
531
     * ```typescript
532
     * // get the nativeElement of the second node
533
     * const node: IgxTreeNode = this.tree.nodes.first();
534
     * const nodeElement: HTMLElement = node.nativeElement;
535
     * ```
536
     */
537
    /** @hidden @internal */
538
    public get nativeElement() {
539
        return this.element.nativeElement;
1,951✔
540
    }
541

542
    /** @hidden @internal */
543
    public ngOnInit() {
544
        this.openAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(
3,631✔
545
            () => {
546
                this.tree.nodeExpanded.emit({ owner: this.tree, node: this });
12✔
547
            }
548
        );
549
        this.closeAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
3,631✔
550
            this.tree.nodeCollapsed.emit({ owner: this.tree, node: this });
3✔
551
            this.treeService.collapse(this);
3✔
552
            this.cdr.markForCheck();
3✔
553
        });
554
    }
555

556
    /**
557
     * @hidden @internal
558
     * Sets the focus to the node's <a> child, if present
559
     * Sets the node as the tree service's focusedNode
560
     * Marks the node as the current active element
561
     */
562
    public handleFocus(): void {
563
        if (this.disabled) {
48!
564
            return;
×
565
        }
566
        if (this.navService.focusedNode !== this) {
48!
567
            this.navService.focusedNode = this;
×
568
        }
569
        this.isFocused = true;
48✔
570
        if (this.linkChildren?.length) {
48✔
571
            this.linkChildren.first.nativeElement.focus();
1✔
572
            return;
1✔
573
        }
574
        if (this.registeredChildren.length) {
47✔
575
            this.registeredChildren[0].elementRef.nativeElement.focus();
1✔
576
            return;
1✔
577
        }
578
    }
579

580
    /**
581
     * @hidden @internal
582
     * Clear the node's focused status
583
     */
584
    public clearFocus(): void {
585
        this.isFocused = false;
28✔
586
    }
587

588
    /**
589
     * @hidden @internal
590
     */
591
    public onSelectorPointerDown(event) {
NEW
592
        event.preventDefault();
×
NEW
593
        event.stopPropagation()
×
594
    }
595

596
    /**
597
     * @hidden @internal
598
     */
599
    public onSelectorClick(event) {
600
        // event.stopPropagation();
601
        event.preventDefault();
28✔
602
        // this.navService.handleFocusedAndActiveNode(this);
603
        if (event.shiftKey) {
28✔
604
            this.selectionService.selectMultipleNodes(this, event);
2✔
605
            return;
2✔
606
        }
607
        if (this.selected) {
26✔
608
            this.selectionService.deselectNode(this, event);
9✔
609
        } else {
610
            this.selectionService.selectNode(this, event);
17✔
611
        }
612
    }
613

614
    /**
615
     * Toggles the node expansion state, triggering animation
616
     *
617
     * ```html
618
     * <igx-tree>
619
     *      <igx-tree-node #node>My Node</igx-tree-node>
620
     * </igx-tree>
621
     * <button type="button" igxButton (click)="node.toggle()">Toggle Node</button>
622
     * ```
623
     *
624
     * ```typescript
625
     * const myNode: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
626
     * myNode.toggle();
627
     * ```
628
     */
629
    public toggle() {
630
        if (this.expanded) {
13✔
631
            this.collapse();
1✔
632
        } else {
633
            this.expand();
12✔
634
        }
635
    }
636

637
    /** @hidden @internal */
638
    public indicatorClick() {
639
        if(!this.tree.toggleNodeOnClick) {
10✔
640
            this.toggle();
10✔
641
            this.navService.setFocusedAndActiveNode(this);
10✔
642
        }
643
    }
644

645
    /**
646
     * @hidden @internal
647
     */
648
    public onPointerDown(event) {
649
        event.stopPropagation();
25✔
650

651
        //Toggle the node only on left mouse click - https://w3c.github.io/pointerevents/#button-states
652
        if(this.tree.toggleNodeOnClick && event.button === 0) {
25✔
653
            this.toggle();
1✔
654
        }
655

656
        this.navService.setFocusedAndActiveNode(this);
25✔
657
    }
658

659
    public override ngOnDestroy() {
660
        super.ngOnDestroy();
3,631✔
661
        this.selectionService.ensureStateOnNodeDelete(this);
3,631✔
662
    }
663

664
    /**
665
     * Expands the node, triggering animation
666
     *
667
     * ```html
668
     * <igx-tree>
669
     *      <igx-tree-node #node>My Node</igx-tree-node>
670
     * </igx-tree>
671
     * <button type="button" igxButton (click)="node.expand()">Expand Node</button>
672
     * ```
673
     *
674
     * ```typescript
675
     * const myNode: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
676
     * myNode.expand();
677
     * ```
678
     */
679
    public expand() {
680
        if (this.expanded && !this.treeService.collapsingNodes.has(this)) {
27✔
681
            return;
1✔
682
        }
683
        const args: ITreeNodeTogglingEventArgs = {
26✔
684
            owner: this.tree,
685
            node: this,
686
            cancel: false
687

688
        };
689
        this.tree.nodeExpanding.emit(args);
26✔
690
        if (!args.cancel) {
26✔
691
            this.treeService.expand(this, true);
25✔
692
            this.cdr.detectChanges();
25✔
693
            this.playOpenAnimation(
25✔
694
                this.childrenContainer
695
            );
696
        }
697
    }
698

699
    /**
700
     * Collapses the node, triggering animation
701
     *
702
     * ```html
703
     * <igx-tree>
704
     *      <igx-tree-node #node>My Node</igx-tree-node>
705
     * </igx-tree>
706
     * <button type="button" igxButton (click)="node.collapse()">Collapse Node</button>
707
     * ```
708
     *
709
     * ```typescript
710
     * const myNode: IgxTreeNode<any> = this.tree.findNodes(data[0])[0];
711
     * myNode.collapse();
712
     * ```
713
     */
714
    public collapse() {
715
        if (!this.expanded || this.treeService.collapsingNodes.has(this)) {
6✔
716
            return;
2✔
717
        }
718
        const args: ITreeNodeTogglingEventArgs = {
4✔
719
            owner: this.tree,
720
            node: this,
721
            cancel: false
722

723
        };
724
        this.tree.nodeCollapsing.emit(args);
4✔
725
        if (!args.cancel) {
4✔
726
            this.treeService.collapsing(this);
3✔
727
            this.playCloseAnimation(
3✔
728
                this.childrenContainer
729
            );
730
        }
731
    }
732

733
    /** @hidden @internal */
734
    public addLinkChild(link: IgxTreeNodeLinkDirective) {
735
        this._tabIndex = -1;
15✔
736
        this.registeredChildren.push(link);
15✔
737
    }
738

739
    /** @hidden @internal */
740
    public removeLinkChild(link: IgxTreeNodeLinkDirective) {
741
        const index = this.registeredChildren.indexOf(link);
30✔
742
        if (index !== -1) {
30✔
743
            this.registeredChildren.splice(index, 1);
15✔
744
        }
745
    }
746
}
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