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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

48.39
/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts
1
import {
2
    AfterContentInit,
3
    AfterViewInit,
4
    booleanAttribute,
5
    ChangeDetectionStrategy,
6
    ChangeDetectorRef,
7
    Component,
8
    ContentChild,
9
    ContentChildren,
10
    ElementRef,
11
    EnvironmentInjector,
12
    EventEmitter,
13
    forwardRef,
14
    Inject,
15
    Injector,
16
    Input,
17
    IterableChangeRecord,
18
    IterableDiffers,
19
    LOCALE_ID,
20
    NgZone,
21
    OnChanges,
22
    OnDestroy,
23
    OnInit,
24
    Output,
25
    QueryList,
26
    TemplateRef,
27
    ViewContainerRef
28
} from '@angular/core';
29
import { IgxHierarchicalGridAPIService } from './hierarchical-grid-api.service';
30
import { DOCUMENT } from '@angular/common';
31
import { IgxFilteringService } from '../filtering/grid-filtering.service';
32
import { IgxGridSummaryService } from '../summaries/grid-summary.service';
33
import { IgxHierarchicalGridBaseDirective } from './hierarchical-grid-base.directive';
34
import { IgxHierarchicalGridNavigationService } from './hierarchical-grid-navigation.service';
35
import { IgxGridSelectionService } from '../selection/selection.service';
36
import { IgxOverlayService } from '../../services/public_api';
37
import { first, filter, takeUntil, pluck } from 'rxjs/operators';
38
import { IgxColumnComponent } from '../columns/column.component';
39
import { ISearchInfo } from '../common/events';
40
import { IgxRowIslandAPIService } from './row-island-api.service';
41
import { PlatformUtil } from '../../core/utils';
42
import { IForOfState } from '../../directives/for-of/for_of.directive';
43
import { IgxColumnResizingService } from '../resizing/resizing.service';
44
import { GridType, IGX_GRID_SERVICE_BASE, IgxGridPaginatorTemplateContext } from '../common/grid.interface';
45
import { IgxGridToolbarDirective, IgxGridToolbarTemplateContext } from '../toolbar/common';
46
import { IgxActionStripToken } from '../../action-strip/token';
47
import { IgxPaginatorDirective } from '../../paginator/paginator-interfaces';
48
import { IgxFlatTransactionFactory } from '../../services/transaction/transaction-factory.service';
49
import { IGridCreatedEventArgs } from './events';
50
import { IgxGridValidationService } from '../grid/grid-validation.service';
51
import { IgxTextHighlightService } from '../../directives/text-highlight/text-highlight.service';
52
import { IgxPaginatorComponent } from '../../paginator/paginator.component';
53

54
/* blazorCopyInheritedMembers */
55
/* blazorElement */
56
/* wcElementTag: igc-row-island */
57
/* blazorIndirectRender */
58
/* jsonAPIManageCollectionInMarkup */
59
/* jsonAPIManageItemInMarkup */
60
/* mustUseNGParentAnchor */
61
/* additionalIdentifier: ChildDataKey */
62
/* contentParent: RowIsland */
63
/* contentParent: HierarchicalGrid */
64
/**
65
 * Row island
66
 *
67
 * @igxModule IgxHierarchicalGridModule
68
 * @igxParent IgxHierarchicalGridComponent, IgxRowIslandComponent
69
 *
70
 */
71
@Component({
72
    changeDetection: ChangeDetectionStrategy.OnPush,
73
    selector: 'igx-row-island',
74
    template: `@if (platform.isElements) {
75
        <div #sink style="display: none;">
76
            <ng-content select="igx-column,igc-column,igx-column-group,igc-column-group,igx-action-strip,igc-action-strip"></ng-content>
77
            <ng-content select="igx-row-island,igc-row-island"></ng-content>
78
        </div>
79
    }`,
80
    providers: [
81
        IgxRowIslandAPIService,
82
        IgxFilteringService,
83
        IgxGridSelectionService
84
    ],
85
    standalone: true
86
})
87
export class IgxRowIslandComponent extends IgxHierarchicalGridBaseDirective
2✔
88
    implements AfterContentInit, AfterViewInit, OnChanges, OnInit, OnDestroy {
89

90
    /* blazorSuppress */
91
    /**
92
     * Sets the key of the row island by which child data would be taken from the row data if such is provided.
93
     * ```html
94
     * <igx-hierarchical-grid [data]="Data" [autoGenerate]="true">
95
     *      <igx-row-island [key]="'childData'">
96
     *          <!-- ... -->
97
     *      </igx-row-island>
98
     * </igx-hierarchical-grid>
99
     * ```
100
     *
101
     * @memberof IgxRowIslandComponent
102
     */
103
    @Input()
104
    public key: string;
105

106
    /* blazorInclude,wcInclude TODO: Move to Elements-only component */
107
    /**
108
     * Sets the key of the row island by which child data would be taken from the row data if such is provided.
109
     * @hidden @internal
110
     */
111
    @Input()
112
    public get childDataKey() {
113
        return this.key;
×
114
    }
115
    /* blazorInclude,wcInclude */
116
    public set childDataKey(value: string) {
117
        this.key = value;
×
118
    }
119

120
    /**
121
     * @hidden
122
     */
123
    @ContentChildren(forwardRef(() => IgxRowIslandComponent), { read: IgxRowIslandComponent, descendants: false })
2✔
124
    public children = new QueryList<IgxRowIslandComponent>();
2✔
125

126
    /* contentChildren */
127
    /* blazorInclude */
128
    /* blazorTreatAsCollection */
129
    /* blazorCollectionName: RowIslandCollection */
130
    /**
131
     * @hidden @internal
132
     */
133
    @ContentChildren(forwardRef(() => IgxRowIslandComponent), { read: IgxRowIslandComponent, descendants: false })
2✔
134
    public childLayoutList = new QueryList<IgxRowIslandComponent>();
2✔
135

136
    /**
137
     * @hidden
138
     */
139
    @ContentChildren(IgxColumnComponent, { read: IgxColumnComponent, descendants: false })
140
    public childColumns = new QueryList<IgxColumnComponent>();
2✔
141

142
    @ContentChild(IgxGridToolbarDirective, { read: TemplateRef })
143
    protected toolbarDirectiveTemplate: TemplateRef<IgxGridToolbarTemplateContext>;
144

145
    @ContentChild(IgxPaginatorDirective, { read: TemplateRef })
146
    protected paginatorDirectiveTemplate: TemplateRef<any>;
147

148
    /* csSuppress */
149
    /**
150
     * Sets/Gets the toolbar template for each child grid created from this row island.
151
    */
152
    @Input()
153
    public get toolbarTemplate(): TemplateRef<IgxGridToolbarTemplateContext> {
UNCOV
154
        return this._toolbarTemplate || this.toolbarDirectiveTemplate;
×
155
    }
156

157
    public set toolbarTemplate(template: TemplateRef<IgxGridToolbarTemplateContext>) {
UNCOV
158
        this._toolbarTemplate = template;
×
159
    }
160

161

162
    /* csSuppress */
163
    /**
164
     * Sets/Gets the paginator template for each child grid created from this row island.
165
    */
166
    @Input()
167
    public get paginatorTemplate(): TemplateRef<IgxGridPaginatorTemplateContext> {
UNCOV
168
        return this._paginatorTemplate || this.paginatorDirectiveTemplate;
×
169
    }
170

171
    public set paginatorTemplate(template: TemplateRef<IgxGridPaginatorTemplateContext>) {
UNCOV
172
        this._paginatorTemplate = template;
×
173
    }
174

175
    // TODO(api-analyzer): Shouldn't need all tags to copy from base or hidden/internal due to include tag
176
    /* contentChildren */
177
    /* blazorInclude */
178
    /* blazorTreatAsCollection */
179
    /* blazorCollectionName: ActionStripCollection */
180
    /* blazorCollectionItemName: ActionStrip */
181
    /* ngQueryListName: actionStripComponents */
182
    /** @hidden @internal */
183
    @ContentChildren(IgxActionStripToken, { read: IgxActionStripToken, descendants: false })
184
    protected override actionStripComponents: QueryList<IgxActionStripToken>;
185

186
    /**
187
     * @hidden
188
     */
189
    @Output()
190
    public layoutChange = new EventEmitter<any>();
2✔
191

192
    /**
193
     * Event emitted when a grid is being created based on this row island.
194
     * ```html
195
     * <igx-hierarchical-grid [data]="Data" [autoGenerate]="true">
196
     *      <igx-row-island [key]="'childData'" (gridCreated)="gridCreated($event)" #rowIsland>
197
     *          <!-- ... -->
198
     *      </igx-row-island>
199
     * </igx-hierarchical-grid>
200
     * ```
201
     *
202
     * @memberof IgxRowIslandComponent
203
     */
204
    @Output()
205
    public gridCreated = new EventEmitter<IGridCreatedEventArgs>();
2✔
206

207
    /**
208
     * Emitted after a grid is being initialized for this row island.
209
     * The emitting is done in `ngAfterViewInit`.
210
     * ```html
211
     * <igx-hierarchical-grid [data]="Data" [autoGenerate]="true">
212
     *      <igx-row-island [key]="'childData'" (gridInitialized)="gridInitialized($event)" #rowIsland>
213
     *          <!-- ... -->
214
     *      </igx-row-island>
215
     * </igx-hierarchical-grid>
216
     * ```
217
     *
218
     * @memberof IgxRowIslandComponent
219
     */
220
    @Output()
221
    public gridInitialized = new EventEmitter<IGridCreatedEventArgs>();
2✔
222

223
    /**
224
     * @hidden
225
     */
226
    public initialChanges = [];
2✔
227

228
    /**
229
     * @hidden
230
     */
231
    public rootGrid: GridType = null;
2✔
232

233
    /** @hidden */
234
    public readonly data: any[] | null;
235

236
    /** @hidden */
237
    public override get hiddenColumnsCount(): number {
238
        return 0;
×
239
    }
240

241
    /** @hidden */
242
    public override get pinnedColumnsCount(): number {
243
        return 0;
×
244
    }
245

246
    /** @hidden */
247
    public override get lastSearchInfo(): ISearchInfo {
248
        return null;
×
249
    }
250

251
    /** @hidden */
252
    public override get filteredData(): any {
253
        return [];
×
254
    }
255

256
    /** @hidden */
257
    public override get filteredSortedData(): any[] {
258
        return [];
×
259
    }
260

261
    /** @hidden */
262
    public override get virtualizationState(): IForOfState {
263
        return null;
×
264
    }
265

266
    /** @hidden */
267
    public override get pinnedColumns(): IgxColumnComponent[] {
UNCOV
268
        return [];
×
269
    }
270

271
    /** @hidden */
272
    public override get unpinnedColumns(): IgxColumnComponent[] {
273
        return [];
×
274
    }
275

276
    /** @hidden */
277
    public override get visibleColumns(): IgxColumnComponent[] {
UNCOV
278
        return [];
×
279
    }
280

281
    /** @hidden */
282
    public override get dataView(): any[] {
283
        return [];
×
284
    }
285

286
    private ri_columnListDiffer;
287
    private layout_id = `igx-row-island-`;
2✔
288
    private isInit = false;
2✔
289
    private _toolbarTemplate: TemplateRef<IgxGridToolbarTemplateContext>;
290
    private _paginatorTemplate: TemplateRef<IgxGridPaginatorTemplateContext>;
291

292
    /**
293
     * Sets if all immediate children of the grids for this `IgxRowIslandComponent` should be expanded/collapsed.
294
     * ```html
295
     * <igx-hierarchical-grid [data]="Data" [autoGenerate]="true">
296
     *      <igx-row-island [key]="'childData'" [expandChildren]="true" #rowIsland>
297
     *          <!-- ... -->
298
     *      </igx-row-island>
299
     * </igx-hierarchical-grid>
300
     * ```
301
     *
302
     * @memberof IgxRowIslandComponent
303
     */
304
    @Input({ transform: booleanAttribute })
305
    public set expandChildren(value: boolean) {
UNCOV
306
        this._defaultExpandState = value;
×
UNCOV
307
        this.rowIslandAPI.getChildGrids().forEach((grid) => {
×
UNCOV
308
            if (this.document.body.contains(grid.nativeElement)) {
×
309
                // Detect changes right away if the grid is visible
UNCOV
310
                grid.expandChildren = value;
×
UNCOV
311
                grid.cdr.detectChanges();
×
312
            } else {
313
                // Else defer the detection on changes when the grid gets into view for performance.
314
                grid.updateOnRender = true;
×
315
            }
316
        });
317
    }
318

319
    /**
320
     * Gets if all immediate children of the grids for this `IgxRowIslandComponent` have been set to be expanded/collapsed.
321
     * ```typescript
322
     * const expanded = this.rowIsland.expandChildren;
323
     * ```
324
     *
325
     * @memberof IgxRowIslandComponent
326
     */
327
    public get expandChildren(): boolean {
UNCOV
328
        return this._defaultExpandState;
×
329
    }
330

331
    /**
332
     * @hidden
333
     */
334
    public get id() {
335
        const pId = this.parentId ? this.parentId.substring(this.parentId.indexOf(this.layout_id) + this.layout_id.length) + '-' : '';
15✔
336
        return this.layout_id + pId + this.key;
15✔
337
    }
338

339
    /**
340
     * @hidden
341
     */
342
    public get parentId() {
343
        return this.parentIsland ? this.parentIsland.id : null;
21✔
344
    }
345

346
    /**
347
     * @hidden
348
     */
349
    public get level() {
UNCOV
350
        let ptr = this.parentIsland;
×
UNCOV
351
        let lvl = 0;
×
UNCOV
352
        while (ptr) {
×
UNCOV
353
            lvl++;
×
UNCOV
354
            ptr = ptr.parentIsland;
×
355
        }
UNCOV
356
        return lvl + 1;
×
357
    }
358

359
    constructor(
360
        validationService: IgxGridValidationService,
361
        selectionService: IgxGridSelectionService,
362
        colResizingService: IgxColumnResizingService,
363
        @Inject(IGX_GRID_SERVICE_BASE) gridAPI: IgxHierarchicalGridAPIService,
364
        transactionFactory: IgxFlatTransactionFactory,
365
        elementRef: ElementRef<HTMLElement>,
366
        zone: NgZone,
367
        @Inject(DOCUMENT) document,
368
        cdr: ChangeDetectorRef,
369
        differs: IterableDiffers,
370
        viewRef: ViewContainerRef,
371
        injector: Injector,
372
        envInjector: EnvironmentInjector,
373
        navigation: IgxHierarchicalGridNavigationService,
374
        filteringService: IgxFilteringService,
375
        textHighlightService: IgxTextHighlightService,
376
        @Inject(IgxOverlayService) overlayService: IgxOverlayService,
377
        summaryService: IgxGridSummaryService,
378
        public rowIslandAPI: IgxRowIslandAPIService,
2✔
379
        @Inject(LOCALE_ID) localeId: string,
380
        platform: PlatformUtil) {
381
        super(
2✔
382
            validationService,
383
            selectionService,
384
            colResizingService,
385
            gridAPI,
386
            transactionFactory,
387
            elementRef,
388
            zone,
389
            document,
390
            cdr,
391
            differs,
392
            viewRef,
393
            injector,
394
            envInjector,
395
            navigation,
396
            filteringService,
397
            textHighlightService,
398
            overlayService,
399
            summaryService,
400
            localeId,
401
            platform
402
        );
403
    }
404

405
    /**
406
     * @hidden
407
     */
408
    public override ngOnInit() {
409
        this.filteringService.grid = this as GridType;
2✔
410
        this.rootGrid = this.gridAPI.grid;
2✔
411
        this.rowIslandAPI.rowIsland = this;
2✔
412
        this.ri_columnListDiffer = this.differs.find([]).create(null);
2✔
413
    }
414

415
    /**
416
     * @hidden
417
     */
418
    public override ngAfterContentInit() {
419
        this.updateChildren();
2✔
420
        this.children.notifyOnChanges();
2✔
421
        this.children.changes.pipe(takeUntil(this.destroy$))
2✔
422
            .subscribe(() => {
UNCOV
423
                this.updateChildren();
×
424
                // update existing grids since their child ri have been changed.
UNCOV
425
                this.getGridsForIsland(this.key).forEach(grid => {
×
UNCOV
426
                    (grid as any).onRowIslandChange(this.children);
×
427
                });
428
            });
429
        const nestedColumns = this.children.map((layout) => layout.columnList.toArray());
2✔
430
        const colsArray = [].concat.apply([], nestedColumns);
2✔
431
        const topCols = this.columnList.filter((item) => colsArray.indexOf(item) === -1);
9✔
432
        this._childColumns = topCols;
2✔
433
        this.updateColumns(this._childColumns);
2✔
434
        this.columnList.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
2✔
UNCOV
435
            Promise.resolve().then(() => {
×
UNCOV
436
                this.updateColumnList();
×
437
            });
438
        });
439

440
        // handle column changes so that they are passed to child grid instances when columnChange is emitted.
441
        this.ri_columnListDiffer.diff(this.childColumns);
2✔
442
        this._childColumns.forEach(x => x.columnChange.pipe(takeUntil(x.destroy$)).subscribe(() => this.updateColumnList()));
6✔
443
        this.childColumns.changes.pipe(takeUntil(this.destroy$)).subscribe((change: QueryList<IgxColumnComponent>) => {
2✔
UNCOV
444
            const diff = this.ri_columnListDiffer.diff(change);
×
UNCOV
445
            if (diff) {
×
UNCOV
446
                diff.forEachAddedItem((record: IterableChangeRecord<IgxColumnComponent>) => {
×
UNCOV
447
                    record.item.columnChange.pipe(takeUntil(record.item.destroy$)).subscribe(() => this.updateColumnList());
×
448
                });
449
            }
450
        });
451

452
        if (this.actionStrip) {
2!
UNCOV
453
            this.actionStrip.menuOverlaySettings.outlet = this.outlet;
×
454
        }
455
    }
456

457
    /**
458
     * @hidden
459
     */
460
    public override ngAfterViewInit() {
461
        this.rowIslandAPI.register(this);
2✔
462
        if (this.parentIsland) {
2✔
463
            this.parentIsland.rowIslandAPI.registerChildRowIsland(this);
1✔
464
        } else {
465
            this.rootGrid.gridAPI.registerChildRowIsland(this);
1✔
466
        }
467
        this._init = false;
2✔
468

469
        // Create the child toolbar if the parent island has a toolbar definition
470
        this.gridCreated.pipe(pluck('grid'), takeUntil(this.destroy$)).subscribe(grid => {
2✔
UNCOV
471
            grid.rendered$.pipe(first(), filter(() => !!this.toolbarTemplate))
×
UNCOV
472
                .subscribe(() => grid.toolbarOutlet.createEmbeddedView(this.toolbarTemplate, { $implicit: grid }, { injector: grid.toolbarOutlet.injector }));
×
UNCOV
473
            grid.rendered$.pipe(first(), filter(() => !!this.paginatorTemplate))
×
474
                .subscribe(() => {
UNCOV
475
                    this.rootGrid.paginatorList.changes.pipe(takeUntil(this.destroy$)).subscribe((changes: QueryList<IgxPaginatorComponent>) => {
×
UNCOV
476
                        changes.forEach(p => {
×
UNCOV
477
                            if (p.nativeElement.offsetParent?.id === grid.id) {
×
478
                                // Optimize update only for those grids that have related changed paginator.
UNCOV
479
                                grid.setUpPaginator()
×
480
                            }
481
                        });
482
                    });
UNCOV
483
                    grid.paginatorOutlet.createEmbeddedView(this.paginatorTemplate, { $implicit: grid });
×
484
                });
485
        });
486
    }
487

488
    /**
489
     * @hidden
490
     */
491
    public ngOnChanges(changes) {
492
        this.layoutChange.emit(changes);
2✔
493
        if (!this.isInit) {
2✔
494
            this.initialChanges.push(changes);
2✔
495
        }
496
    }
497

498
    /**
499
     * @hidden
500
     */
501
    public override ngOnDestroy() {
502
        // Override the base destroy because we have not rendered anything to use removeEventListener on
503
        this.destroy$.next(true);
2✔
504
        this.destroy$.complete();
2✔
505
        this._destroyed = true;
2✔
506
        this.rowIslandAPI.unset(this.id);
2✔
507
        if (this.parentIsland) {
2✔
508
            this.getGridsForIsland(this.key).forEach(grid => {
1✔
UNCOV
509
                this.cleanGridState(grid);
×
UNCOV
510
                grid.gridAPI.unsetChildRowIsland(this);
×
511
            });
512
            this.parentIsland.rowIslandAPI.unsetChildRowIsland(this);
1✔
513
        } else {
514
            this.rootGrid.gridAPI.unsetChildRowIsland(this);
1✔
515
            this.cleanGridState(this.rootGrid);
1✔
516
        }
517
    }
518

519
    /**
520
     * @hidden
521
     */
522
    public override reflow() { }
523

524
    /**
525
     * @hidden
526
     */
527
    public override calculateGridHeight() { }
528

529
    /**
530
     * @hidden
531
     */
532
    public override calculateGridWidth() { }
533

534
    protected _childColumns = [];
2✔
535

536
    protected updateColumnList() {
UNCOV
537
        const nestedColumns = this.children.map((layout) => layout.columnList.toArray());
×
UNCOV
538
        const colsArray = [].concat.apply([], nestedColumns);
×
UNCOV
539
        const topCols = this.columnList.filter((item) => {
×
UNCOV
540
            if (colsArray.indexOf(item) === -1) {
×
541
                /* Reset the default width of the columns that come into this row island,
542
                because the root catches them first during the detectChanges() and sets their defaultWidth. */
UNCOV
543
                item.defaultWidth = undefined;
×
UNCOV
544
                return true;
×
545
            }
UNCOV
546
            return false;
×
547
        });
UNCOV
548
        this._childColumns = topCols;
×
UNCOV
549
        this.updateColumns(this._childColumns);
×
UNCOV
550
        this.rowIslandAPI.getChildGrids().forEach((grid: GridType) => {
×
UNCOV
551
            grid.createColumnsList(this._childColumns);
×
UNCOV
552
            if (!this.document.body.contains(grid.nativeElement)) {
×
UNCOV
553
                grid.updateOnRender = true;
×
554
            }
555
        });
556
    }
557

558
    protected updateChildren() {
559
        if (this.children.first === this) {
2!
560
            this.children.reset(this.children.toArray().slice(1));
×
561
        }
562
        this.children.forEach(child => {
2✔
563
            child.parentIsland = this;
1✔
564
        });
565
    }
566

567
    private cleanGridState(grid) {
568
        grid.childGridTemplates.forEach((tmpl) => {
1✔
UNCOV
569
            tmpl.owner.cleanView(tmpl.context.templateID);
×
570
        });
571
        grid.childGridTemplates.clear();
1✔
572
        grid.onRowIslandChange();
1✔
573
    }
574
}
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

© 2025 Coveralls, Inc