• 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

2.36
/projects/igniteui-angular/button-group/src/button-group/button-group.component.ts
1
import { AfterViewInit, Component, ContentChildren, ChangeDetectorRef, EventEmitter, HostBinding, Input, Output, QueryList, Renderer2, ViewChildren, OnDestroy, ElementRef, booleanAttribute, inject } from '@angular/core';
2
import { Subject } from 'rxjs';
3
import { IgxButtonDirective } from 'igniteui-angular/directives';
4
import { IgxRippleDirective } from 'igniteui-angular/directives';
5

6
import { takeUntil } from 'rxjs/operators';
7
import { IBaseEventArgs } from 'igniteui-angular/core';
8
import { IgxIconComponent } from 'igniteui-angular/icon';
9

10
/**
11
 * Determines the Button Group alignment
12
 */
13
export const ButtonGroupAlignment = {
3✔
14
    horizontal: 'horizontal',
15
    vertical: 'vertical'
16
} as const;
17
export type ButtonGroupAlignment = typeof ButtonGroupAlignment[keyof typeof ButtonGroupAlignment];
18

19
let NEXT_ID = 0;
3✔
20

21
/**
22
 * **Ignite UI for Angular Button Group** -
23
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/buttongroup.html)
24
 *
25
 * The Ignite UI Button Group displays a group of buttons either vertically or horizontally. The group supports
26
 * single, multi and singleRequired selection.
27
 *
28
 * Example:
29
 * ```html
30
 * <igx-buttongroup selectionMode="multi" [values]="fontOptions">
31
 * </igx-buttongroup>
32
 * ```
33
 * The `fontOptions` value shown above is defined as:
34
 * ```typescript
35
 * this.fontOptions = [
36
 *   { icon: 'format_bold', selected: false },
37
 *   { icon: 'format_italic', selected: false },
38
 *   { icon: 'format_underlined', selected: false }];
39
 * ```
40
 */
41
@Component({
42
    selector: 'igx-buttongroup',
43
    templateUrl: 'button-group-content.component.html',
44
    imports: [IgxButtonDirective, IgxRippleDirective, IgxIconComponent]
45
})
46
export class IgxButtonGroupComponent implements AfterViewInit, OnDestroy {
3✔
UNCOV
47
    private _cdr = inject(ChangeDetectorRef);
×
UNCOV
48
    private _renderer = inject(Renderer2);
×
UNCOV
49
    private _el = inject(ElementRef);
×
50

51
    /**
52
     * A collection containing all buttons inside the button group.
53
     */
54
    public get buttons(): IgxButtonDirective[] {
UNCOV
55
        return [...this.viewButtons.toArray(), ...this.templateButtons.toArray()];
×
56
    }
57

58
    /**
59
     * Gets/Sets the value of the `id` attribute. If not set it will be automatically generated.
60
     * ```html
61
     *  <igx-buttongroup [id]="'igx-dialog-56'" [selectionMode]="'multi'" [values]="alignOptions">
62
     * ```
63
     */
64
    @HostBinding('attr.id')
65
    @Input()
UNCOV
66
    public id = `igx-buttongroup-${NEXT_ID++}`;
×
67

68
    /**
69
     * @hidden
70
     */
71
    @HostBinding('style.zIndex')
UNCOV
72
    public zIndex = 0;
×
73

74
    /**
75
     * Allows you to set a style using the `itemContentCssClass` input.
76
     * The value should be the CSS class name that will be applied to the button group.
77
     * ```typescript
78
     * public style1 = "styleClass";
79
     *  //..
80
     * ```
81
     *  ```html
82
     * <igx-buttongroup [itemContentCssClass]="style1" [selectionMode]="'multi'" [values]="alignOptions">
83
     * ```
84
     */
85
    @Input()
86
    public set itemContentCssClass(value: string) {
UNCOV
87
        this._itemContentCssClass = value || this._itemContentCssClass;
×
88
    }
89

90
    /**
91
     * Returns the CSS class of the item content of the `IgxButtonGroup`.
92
     * ```typescript
93
     *  @ViewChild("MyChild")
94
     * public buttonG: IgxButtonGroupComponent;
95
     * ngAfterViewInit(){
96
     *    let buttonSelect = this.buttonG.itemContentCssClass;
97
     * }
98
     * ```
99
     */
100
    public get itemContentCssClass(): string {
UNCOV
101
        return this._itemContentCssClass;
×
102
    }
103

104
    /**
105
     * Enables selecting multiple buttons. By default, multi-selection is false.
106
     *
107
     * @deprecated in version 16.1.0. Use the `selectionMode` property instead.
108
     */
109
    @Input()
110
    public get multiSelection() {
111
        if (this.selectionMode === 'multi') {
×
112
            return true;
×
113
        } else {
114
            return false;
×
115
        }
116
    }
117
    public set multiSelection(selectionMode: boolean) {
118
        if (selectionMode) {
×
119
            this.selectionMode = 'multi';
×
120
        } else {
121
            this.selectionMode = 'single';
×
122
        }
123
    }
124

125
    /**
126
     * Gets/Sets the selection mode to 'single', 'singleRequired' or 'multi' of the buttons. By default, the selection mode is 'single'.
127
     * ```html
128
     * <igx-buttongroup [selectionMode]="'multi'" [alignment]="alignment"></igx-buttongroup>
129
     * ```
130
     */
131
    @Input()
132
    public get selectionMode() {
UNCOV
133
        return this._selectionMode;
×
134
    }
135
    public set selectionMode(selectionMode: 'single' | 'singleRequired' | 'multi') {
UNCOV
136
        if (this.viewButtons && selectionMode !== this._selectionMode) {
×
UNCOV
137
            this.buttons.forEach((b,i) => {
×
UNCOV
138
                this.deselectButton(i);
×
139
            });
UNCOV
140
            this._selectionMode = selectionMode;
×
141
        } else {
UNCOV
142
            this._selectionMode = selectionMode;
×
143
        }
144
    }
145

146
    /**
147
     * Property that configures the buttons in the button group using a collection of `Button` objects.
148
     * ```typescript
149
     *  public ngOnInit() {
150
     *      this.cities = [
151
     *        new Button({
152
     *          label: "Sofia"
153
     *      }),
154
     *        new Button({
155
     *          label: "London"
156
     *      }),
157
     *        new Button({
158
     *          label: "New York",
159
     *          selected: true
160
     *      }),
161
     *        new Button({
162
     *          label: "Tokyo"
163
     *      })
164
     *  ];
165
     *  }
166
     *  //..
167
     * ```
168
     * ```html
169
     *  <igx-buttongroup [selectionMode]="'single'" [values]="cities"></igx-buttongroup>
170
     * ```
171
     */
172
    @Input() public values: any;
173

174
    /**
175
     * Disables the `igx-buttongroup` component. By default it's false.
176
     * ```html
177
     * <igx-buttongroup [disabled]="true" [selectionMode]="'multi'" [values]="fontOptions"></igx-buttongroup>
178
     * ```
179
     */
180
    @Input({ transform: booleanAttribute })
181
    public get disabled(): boolean {
UNCOV
182
        return this._disabled;
×
183
    }
184
    public set disabled(value: boolean) {
185
        if (this._disabled !== value) {
×
186
            this._disabled = value;
×
187

188
            if (this.viewButtons && this.templateButtons) {
×
189
                this.buttons.forEach((b) => (b.disabled = this._disabled));
×
190
            }
191
        }
192
    }
193

194
    /**
195
     * Allows you to set the button group alignment.
196
     * Available options are `ButtonGroupAlignment.horizontal` (default) and `ButtonGroupAlignment.vertical`.
197
     * ```typescript
198
     * public alignment = ButtonGroupAlignment.vertical;
199
     * //..
200
     * ```
201
     * ```html
202
     * <igx-buttongroup [selectionMode]="'single'" [values]="cities" [alignment]="alignment"></igx-buttongroup>
203
     * ```
204
     */
205
    @Input()
206
    public set alignment(value: ButtonGroupAlignment) {
UNCOV
207
        this._isVertical = value === ButtonGroupAlignment.vertical;
×
208
    }
209
    /**
210
     * Returns the alignment of the `igx-buttongroup`.
211
     * ```typescript
212
     * @ViewChild("MyChild")
213
     * public buttonG: IgxButtonGroupComponent;
214
     * ngAfterViewInit(){
215
     *    let buttonAlignment = this.buttonG.alignment;
216
     * }
217
     * ```
218
     */
219
    public get alignment(): ButtonGroupAlignment {
UNCOV
220
        return this._isVertical ? ButtonGroupAlignment.vertical : ButtonGroupAlignment.horizontal;
×
221
    }
222

223
    /**
224
     * An @Ouput property that emits an event when a button is selected.
225
     * ```typescript
226
     * @ViewChild("toast")
227
     * private toast: IgxToastComponent;
228
     * public selectedHandler(buttongroup) {
229
     *     this.toast.open()
230
     * }
231
     *  //...
232
     * ```
233
     * ```html
234
     * <igx-buttongroup #MyChild [selectionMode]="'multi'" (selected)="selectedHandler($event)"></igx-buttongroup>
235
     * <igx-toast #toast>You have made a selection!</igx-toast>
236
     * ```
237
     */
238
    @Output()
UNCOV
239
    public selected = new EventEmitter<IButtonGroupEventArgs>();
×
240

241
    /**
242
     * An @Ouput property that emits an event when a button is deselected.
243
     * ```typescript
244
     *  @ViewChild("toast")
245
     *  private toast: IgxToastComponent;
246
     *  public deselectedHandler(buttongroup){
247
     *     this.toast.open()
248
     * }
249
     *  //...
250
     * ```
251
     * ```html
252
     * <igx-buttongroup> #MyChild [selectionMode]="'multi'" (deselected)="deselectedHandler($event)"></igx-buttongroup>
253
     * <igx-toast #toast>You have deselected a button!</igx-toast>
254
     * ```
255
     */
256
    @Output()
UNCOV
257
    public deselected = new EventEmitter<IButtonGroupEventArgs>();
×
258

259
    @ViewChildren(IgxButtonDirective) private viewButtons: QueryList<IgxButtonDirective>;
260
    @ContentChildren(IgxButtonDirective) private templateButtons: QueryList<IgxButtonDirective>;
261

262
    /**
263
     * Returns true if the `igx-buttongroup` alignment is vertical.
264
     * Note that in order for the accessor to work correctly the property should be set explicitly.
265
     * ```html
266
     * <igx-buttongroup #MyChild [alignment]="alignment" [values]="alignOptions">
267
     * ```
268
     * ```typescript
269
     * //...
270
     * @ViewChild("MyChild")
271
     * private buttonG: IgxButtonGroupComponent;
272
     * ngAfterViewInit(){
273
     *    let orientation = this.buttonG.isVertical;
274
     * }
275
     * ```
276
     */
277
    public get isVertical(): boolean {
UNCOV
278
        return this._isVertical;
×
279
    }
280

281
    /**
282
     * @hidden
283
     */
UNCOV
284
    public selectedIndexes: number[] = [];
×
285

UNCOV
286
    protected buttonClickNotifier$ = new Subject<void>();
×
UNCOV
287
    protected queryListNotifier$ = new Subject<void>();
×
288

289
    private _isVertical: boolean;
290
    private _itemContentCssClass: string;
UNCOV
291
    private _disabled = false;
×
UNCOV
292
    private _selectionMode: 'single' | 'singleRequired' | 'multi' = 'single';
×
293

294
    private mutationObserver: MutationObserver;
UNCOV
295
    private observerConfig: MutationObserverInit = {
×
296
      attributeFilter: ["data-selected"],
297
      childList: true,
298
      subtree: true,
299
    };
300

301
    /**
302
     * Gets the selected button/buttons.
303
     * ```typescript
304
     * @ViewChild("MyChild")
305
     * private buttonG: IgxButtonGroupComponent;
306
     * ngAfterViewInit(){
307
     *    let selectedButton = this.buttonG.selectedButtons;
308
     * }
309
     * ```
310
     */
311
    public get selectedButtons(): IgxButtonDirective[] {
UNCOV
312
        return this.buttons.filter((_, i) => this.selectedIndexes.indexOf(i) !== -1);
×
313
    }
314

315
    /**
316
     * Selects a button by its index.
317
     * ```typescript
318
     * @ViewChild("MyChild")
319
     * private buttonG: IgxButtonGroupComponent;
320
     * ngAfterViewInit(){
321
     *    this.buttonG.selectButton(2);
322
     *    this.cdr.detectChanges();
323
     * }
324
     * ```
325
     *
326
     * @memberOf {@link IgxButtonGroupComponent}
327
     */
328
    public selectButton(index: number) {
UNCOV
329
        if (index >= this.buttons.length || index < 0) {
×
UNCOV
330
            return;
×
331
        }
332

UNCOV
333
        const button = this.buttons[index];
×
UNCOV
334
        button.select();
×
335
    }
336

337
    /**
338
     * @hidden
339
     * @internal
340
     */
341
    public updateSelected(index: number) {
UNCOV
342
        const button = this.buttons[index];
×
343

UNCOV
344
        if (this.selectedIndexes.indexOf(index) === -1) {
×
UNCOV
345
            this.selectedIndexes.push(index);
×
346
        }
347

UNCOV
348
        this._renderer.setAttribute(button.nativeElement, 'aria-pressed', 'true');
×
UNCOV
349
        this._renderer.addClass(button.nativeElement, 'igx-button-group__item--selected');
×
350

UNCOV
351
        const indexInViewButtons = this.viewButtons.toArray().indexOf(button);
×
UNCOV
352
        if (indexInViewButtons !== -1) {
×
UNCOV
353
            this.values[indexInViewButtons].selected = true;
×
354
        }
355

356
        // deselect other buttons if selectionMode is not multi
UNCOV
357
        if (this.selectionMode !== 'multi' && this.selectedIndexes.length > 1) {
×
UNCOV
358
            this.buttons.forEach((_, i) => {
×
UNCOV
359
                if (i !== index && this.selectedIndexes.indexOf(i) !== -1) {
×
UNCOV
360
                    this.deselectButton(i);
×
UNCOV
361
                    this.updateDeselected(i);
×
362
                }
363
            });
364
        }
365

366
    }
367

368
    public updateDeselected(index: number) {
UNCOV
369
        const button = this.buttons[index];
×
UNCOV
370
        if (this.selectedIndexes.indexOf(index) !== -1) {
×
UNCOV
371
            this.selectedIndexes.splice(this.selectedIndexes.indexOf(index), 1);
×
372
        }
373

UNCOV
374
        this._renderer.setAttribute(button.nativeElement, 'aria-pressed', 'false');
×
UNCOV
375
        this._renderer.removeClass(button.nativeElement, 'igx-button-group__item--selected');
×
376

UNCOV
377
        const indexInViewButtons = this.viewButtons.toArray().indexOf(button);
×
UNCOV
378
        if (indexInViewButtons !== -1) {
×
UNCOV
379
            this.values[indexInViewButtons].selected = false;
×
380
        }
381
    }
382

383
    /**
384
     * Deselects a button by its index.
385
     * ```typescript
386
     * @ViewChild("MyChild")
387
     * private buttonG: IgxButtonGroupComponent;
388
     * ngAfterViewInit(){
389
     *    this.buttonG.deselectButton(2);
390
     *    this.cdr.detectChanges();
391
     * }
392
     * ```
393
     *
394
     * @memberOf {@link IgxButtonGroupComponent}
395
     */
396
    public deselectButton(index: number) {
UNCOV
397
        if (index >= this.buttons.length || index < 0) {
×
UNCOV
398
            return;
×
399
        }
400

UNCOV
401
        const button = this.buttons[index];
×
UNCOV
402
        button.deselect();
×
403
    }
404

405
    /**
406
     * @hidden
407
     */
408
    public ngAfterViewInit() {
UNCOV
409
        const initButtons = () => {
×
410
            // Cancel any existing buttonClick subscriptions
UNCOV
411
            this.buttonClickNotifier$.next();
×
412

UNCOV
413
            this.selectedIndexes.splice(0, this.selectedIndexes.length);
×
414

415
            // initial configuration
UNCOV
416
            this.buttons.forEach((button, index) => {
×
UNCOV
417
                const buttonElement = button.nativeElement;
×
UNCOV
418
                this._renderer.addClass(buttonElement, 'igx-button-group__item');
×
419

UNCOV
420
                if (this.disabled) {
×
421
                    button.disabled = true;
×
422
                }
423

UNCOV
424
                if (button.selected) {
×
UNCOV
425
                    this.updateSelected(index);
×
426
                }
427

UNCOV
428
                button.buttonClick.pipe(takeUntil(this.buttonClickNotifier$)).subscribe((_) => this._clickHandler(index));
×
429
            });
430
        };
431

UNCOV
432
        this.mutationObserver = this.setMutationsObserver();
×
433

UNCOV
434
        this.viewButtons.changes.pipe(takeUntil(this.queryListNotifier$)).subscribe(() => {
×
435
            this.mutationObserver.disconnect();
×
436
            initButtons();
×
437
            this.mutationObserver?.observe(this._el.nativeElement, this.observerConfig);
×
438
        });
UNCOV
439
        this.templateButtons.changes.pipe(takeUntil(this.queryListNotifier$)).subscribe(() => {
×
440
            this.mutationObserver.disconnect();
×
441
            initButtons();
×
442
            this.mutationObserver?.observe(this._el.nativeElement, this.observerConfig);
×
443
        });
444

UNCOV
445
        initButtons();
×
UNCOV
446
        this._cdr.detectChanges();
×
UNCOV
447
        this.mutationObserver?.observe(this._el.nativeElement, this.observerConfig);
×
448
    }
449

450
    /**
451
     * @hidden
452
     */
453
    public ngOnDestroy() {
UNCOV
454
        this.buttonClickNotifier$.next();
×
UNCOV
455
        this.buttonClickNotifier$.complete();
×
456

UNCOV
457
        this.queryListNotifier$.next();
×
UNCOV
458
        this.queryListNotifier$.complete();
×
459

UNCOV
460
        this.mutationObserver?.disconnect();
×
461
    }
462

463
    /**
464
     * @hidden
465
     */
466
    public _clickHandler(index: number) {
UNCOV
467
        const button = this.buttons[index];
×
UNCOV
468
        const args: IButtonGroupEventArgs = { owner: this, button, index };
×
469

UNCOV
470
        if (this.selectionMode !== 'multi') {
×
UNCOV
471
            this.buttons.forEach((b, i) => {
×
UNCOV
472
                if (i !== index && this.selectedIndexes.indexOf(i) !== -1) {
×
UNCOV
473
                    this.deselected.emit({ owner: this, button: b, index: i });
×
474
                }
475
            });
476
        }
477

UNCOV
478
        if (this.selectedIndexes.indexOf(index) === -1) {
×
UNCOV
479
            this.selectButton(index);
×
UNCOV
480
            this.selected.emit(args);
×
481
        } else {
UNCOV
482
            if (this.selectionMode !== 'singleRequired') {
×
UNCOV
483
                this.deselectButton(index);
×
UNCOV
484
                this.deselected.emit(args);
×
485
            }
486
        }
487
    }
488

489
    private setMutationsObserver() {
UNCOV
490
        if (typeof MutationObserver !== 'undefined') {
×
UNCOV
491
            return new MutationObserver((records, observer) => {
×
492
                // Stop observing while handling changes
UNCOV
493
                observer.disconnect();
×
494

UNCOV
495
                const updatedButtons = this.getUpdatedButtons(records);
×
496

UNCOV
497
                if (updatedButtons.length > 0) {
×
UNCOV
498
                    updatedButtons.forEach((button) => {
×
UNCOV
499
                        const index = this.buttons.map((b) => b.nativeElement).indexOf(button);
×
500

UNCOV
501
                        this.updateButtonSelectionState(index);
×
502
                    });
503
                }
504

505
                // Watch for changes again
UNCOV
506
                observer.observe(this._el.nativeElement, this.observerConfig);
×
507
            });
508
        }
509
    }
510

511
    private getUpdatedButtons(records: MutationRecord[]) {
UNCOV
512
        const updated: HTMLButtonElement[] = [];
×
513

UNCOV
514
        records
×
UNCOV
515
          .filter((x) => x.type === 'attributes')
×
516
          .reduce((prev, curr) => {
UNCOV
517
            prev.push(
×
518
              curr.target as HTMLButtonElement
519
            );
UNCOV
520
            return prev;
×
521
          }, updated);
522

UNCOV
523
        return updated;
×
524
    }
525

526
    private updateButtonSelectionState(index: number) {
UNCOV
527
        if (this.buttons[index].selected) {
×
UNCOV
528
            this.updateSelected(index);
×
529
        } else {
UNCOV
530
            this.updateDeselected(index);
×
531
        }
532
    }
533
}
534

535
export interface IButtonGroupEventArgs extends IBaseEventArgs {
536
    owner: IgxButtonGroupComponent;
537
    button: IgxButtonDirective;
538
    index: number;
539
}
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