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

IgniteUI / igniteui-angular / 12300330513

12 Dec 2024 04:10PM CUT coverage: 91.592% (-0.03%) from 91.621%
12300330513

push

github

web-flow
Merge pull request #15120 from IgniteUI/19.0.x

Mass Merge 19.0.x to master

12984 of 15225 branches covered (85.28%)

86 of 106 new or added lines in 16 files covered. (81.13%)

12 existing lines in 3 files now uncovered.

26320 of 28736 relevant lines covered (91.59%)

34007.75 hits per line

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

95.7
/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts
1
import {
2
    Component,
3
    EventEmitter,
4
    HostListener,
5
    HostBinding,
6
    Input,
7
    Output,
8
    ViewChild,
9
    ElementRef,
10
    AfterViewInit,
11
    ChangeDetectorRef,
12
    Renderer2,
13
    Optional,
14
    Self,
15
    booleanAttribute,
16
    inject,
17
    DestroyRef,
18
    Inject
19
} from '@angular/core';
20
import { ControlValueAccessor, NgControl, Validators } from '@angular/forms';
21
import { IgxRippleDirective } from '../directives/ripple/ripple.directive';
22
import { IBaseEventArgs, getComponentTheme, mkenum } from '../core/utils';
23
import { EditorProvider, EDITOR_PROVIDER } from '../core/edit-provider';
24
import { noop, Subject } from 'rxjs';
25
import { takeUntil } from 'rxjs/operators';
26
import { IgxTheme, THEME_TOKEN, ThemeToken } from '../services/theme/theme.token';
27

28
export const LabelPosition = /*@__PURE__*/mkenum({
2✔
29
    BEFORE: 'before',
30
    AFTER: 'after'
31
});
32
export type LabelPosition = typeof LabelPosition[keyof typeof LabelPosition];
33

34
export interface IChangeCheckboxEventArgs extends IBaseEventArgs {
35
    checked: boolean;
36
    value?: any;
37
}
38

39
let nextId = 0;
2✔
40
/**
41
 * Allows users to make a binary choice for a certain condition.
42
 *
43
 * @igxModule IgxCheckboxModule
44
 *
45
 * @igxTheme igx-checkbox-theme
46
 *
47
 * @igxKeywords checkbox, label
48
 *
49
 * @igxGroup Data entry and display
50
 *
51
 * @remarks
52
 * The Ignite UI Checkbox is a selection control that allows users to make a binary choice for a certain condition.It behaves similarly
53
 * to the native browser checkbox.
54
 *
55
 * @example
56
 * ```html
57
 * <igx-checkbox [checked]="true">
58
 *   simple checkbox
59
 * </igx-checkbox>
60
 * ```
61
 */
62
@Component({
63
    selector: 'igx-checkbox',
64
    providers: [{
65
            provide: EDITOR_PROVIDER,
66
            useExisting: IgxCheckboxComponent,
67
            multi: true
68
        }],
69
    preserveWhitespaces: false,
70
    templateUrl: 'checkbox.component.html',
71
    imports: [IgxRippleDirective]
72
})
73
export class IgxCheckboxComponent implements EditorProvider, AfterViewInit, ControlValueAccessor {
2✔
74

75
    /**
76
     * An event that is emitted after the checkbox state is changed.
77
     * Provides references to the `IgxCheckboxComponent` and the `checked` property as event arguments.
78
     */
79
    // eslint-disable-next-line @angular-eslint/no-output-native
80
    @Output() public readonly change: EventEmitter<IChangeCheckboxEventArgs> = new EventEmitter<IChangeCheckboxEventArgs>();
8,189✔
81

82
    /**
83
     * @hidden
84
     * @internal
85
     */
86
    public destroy$ = new Subject<boolean>();
8,189✔
87

88
    /**
89
     * Returns reference to the native checkbox element.
90
     *
91
     * @example
92
     * ```typescript
93
     * let checkboxElement =  this.component.checkboxElement;
94
     * ```
95
     */
96
    @ViewChild('checkbox', { static: true })
97
    public nativeInput: ElementRef;
98

99
    /**
100
     * Returns reference to the native label element.
101
     * ```typescript
102
     *
103
     * @example
104
     * let labelElement =  this.component.nativeLabel;
105
     * ```
106
     */
107
    @ViewChild('label', { static: true })
108
    public nativeLabel: ElementRef;
109

110
    /**
111
     * Returns reference to the `nativeElement` of the igx-checkbox/igx-switch.
112
     *
113
     * @example
114
     * ```typescript
115
     * let nativeElement = this.component.nativeElement;
116
     * ```
117
     */
118
    public get nativeElement() {
119
        return this.nativeInput.nativeElement;
116,738✔
120
    }
121

122
    /**
123
     * Returns reference to the label placeholder element.
124
     * ```typescript
125
     *
126
     * @example
127
     * let labelPlaceholder =  this.component.placeholderLabel;
128
     * ```
129
     */
130
    @ViewChild('placeholderLabel', { static: true })
131
    public placeholderLabel: ElementRef;
132

133
    /**
134
     * Sets/gets the `id` of the checkbox component.
135
     * If not set, the `id` of the first checkbox component will be `"igx-checkbox-0"`.
136
     *
137
     * @example
138
     * ```html
139
     * <igx-checkbox id="my-first-checkbox"></igx-checkbox>
140
     * ```
141
     * ```typescript
142
     * let checkboxId =  this.checkbox.id;
143
     * ```
144
     */
145
    @HostBinding('attr.id')
146
    @Input()
147
    public id = `igx-checkbox-${nextId++}`;
8,189✔
148

149
    /**
150
     * Sets/gets the id of the `label` element.
151
     * If not set, the id of the `label` in the first checkbox component will be `"igx-checkbox-0-label"`.
152
     *
153
     * @example
154
     * ```html
155
     * <igx-checkbox labelId="Label1"></igx-checkbox>
156
     * ```
157
     * ```typescript
158
     * let labelId =  this.component.labelId;
159
     * ```
160
     */
161
    @Input() public labelId = `${this.id}-label`;
8,189✔
162

163
    /**
164
     * Sets/gets the `value` attribute.
165
     *
166
     * @example
167
     * ```html
168
     * <igx-checkbox [value]="'CheckboxValue'"></igx-checkbox>
169
     * ```
170
     * ```typescript
171
     * let value =  this.checkbox.value;
172
     * ```
173
     */
174
    @Input() public value: any;
175

176
    /**
177
     * Sets/gets the `name` attribute.
178
     *
179
     * @example
180
     * ```html
181
     * <igx-checkbox name="Checkbox1"></igx-checkbox>
182
     * ```
183
     * ```typescript
184
     * let name =  this.checkbox.name;
185
     * ```
186
     */
187
    @Input() public name: string;
188

189
    /**
190
     * Sets/gets the value of the `tabindex` attribute.
191
     *
192
     * @example
193
     * ```html
194
     * <igx-checkbox [tabindex]="1"></igx-checkbox>
195
     * ```
196
     * ```typescript
197
     * let tabIndex =  this.checkbox.tabindex;
198
     * ```
199
     */
200
    @Input() public tabindex: number = null;
8,189✔
201

202
    /**
203
     *  Sets/gets the position of the `label`.
204
     *  If not set, the `labelPosition` will have value `"after"`.
205
     *
206
     * @example
207
     * ```html
208
     * <igx-checkbox labelPosition="before"></igx-checkbox>
209
     * ```
210
     * ```typescript
211
     * let labelPosition =  this.checkbox.labelPosition;
212
     * ```
213
     */
214
    @Input()
215
    public labelPosition: LabelPosition | string = LabelPosition.AFTER;
8,189✔
216

217
    /**
218
     * Enables/Disables the ripple effect.
219
     * If not set, `disableRipple` will have value `false`.
220
     *
221
     * @example
222
     * ```html
223
     * <igx-checkbox [disableRipple]="true"></igx-checkbox>
224
     * ```
225
     * ```typescript
226
     * let isRippleDisabled = this.checkbox.desableRipple;
227
     * ```
228
     */
229
    @Input({ transform: booleanAttribute })
230
    public disableRipple = false;
8,189✔
231

232
    /**
233
     * Sets/gets whether the checkbox is required.
234
     * If not set, `required` will have value `false`.
235
     *
236
     * @example
237
     * ```html
238
     * <igx-checkbox required></igx-checkbox>
239
     * ```
240
     * ```typescript
241
     * let isRequired = this.checkbox.required;
242
     * ```
243
     */
244
    @Input({ transform: booleanAttribute })
245
    public get required(): boolean {
246
        return this._required || this.nativeElement.hasAttribute('required');
116,886✔
247
    }
248
    public set required(value: boolean) {
249
        this._required = value;
60✔
250
    }
251

252
    /**
253
     * Sets/gets the `aria-labelledby` attribute.
254
     * If not set, the `aria-labelledby` will be equal to the value of `labelId` attribute.
255
     *
256
     * @example
257
     * ```html
258
     * <igx-checkbox aria-labelledby="Checkbox1"></igx-checkbox>
259
     * ```
260
     * ```typescript
261
     * let ariaLabelledBy = this.checkbox.ariaLabelledBy;
262
     * ```
263
     */
264
    @Input('aria-labelledby')
265
    public ariaLabelledBy = this.labelId;
8,189✔
266

267
    /**
268
     * Sets/gets the value of the `aria-label` attribute.
269
     *
270
     * @example
271
     * ```html
272
     * <igx-checkbox aria-label="Checkbox1"></igx-checkbox>
273
     * ```
274
     * ```typescript
275
     * let ariaLabel = this.checkbox.ariaLabel;
276
     * ```
277
     */
278
    @Input('aria-label')
279
    public ariaLabel: string | null = null;
8,189✔
280

281
    /**
282
     * Returns the class of the checkbox component.
283
     *
284
     * @example
285
     * ```typescript
286
     * let class = this.checkbox.cssClass;
287
     * ```
288
     */
289
    @HostBinding('class.igx-checkbox')
290
    public cssClass = 'igx-checkbox';
8,189✔
291

292
    /**
293
     * Returns if the component is of type `material`.
294
     *
295
     * @example
296
     * ```typescript
297
     * let checkbox = this.checkbox.material;
298
     * ```
299
     */
300
    @HostBinding('class.igx-checkbox--material')
301
    protected get material() {
302
        return this.theme === 'material';
58,426✔
303
    }
304

305
    /**
306
     * Returns if the component is of type `indigo`.
307
     *
308
     * @example
309
     * ```typescript
310
     * let checkbox = this.checkbox.indigo;
311
     * ```
312
     */
313
    @HostBinding('class.igx-checkbox--indigo')
314
    protected get indigo() {
315
        return this.theme === 'indigo';
58,426✔
316
    }
317

318
    /**
319
     * Returns if the component is of type `bootstrap`.
320
     *
321
     * @example
322
     * ```typescript
323
     * let checkbox = this.checkbox.bootstrap;
324
     * ```
325
     */
326
    @HostBinding('class.igx-checkbox--bootstrap')
327
    protected get bootstrap() {
328
        return this.theme === 'bootstrap';
58,426✔
329
    }
330

331
    /**
332
     * Returns if the component is of type `fluent`.
333
     *
334
     * @example
335
     * ```typescript
336
     * let checkbox = this.checkbox.fluent;
337
     * ```
338
     */
339
    @HostBinding('class.igx-checkbox--fluent')
340
    protected get fluent() {
341
        return this.theme === 'fluent';
58,426✔
342
    }
343

344
    /**
345
     * Sets/gets whether the checkbox component is on focus.
346
     * Default value is `false`.
347
     *
348
     * @example
349
     * ```typescript
350
     * this.checkbox.focused =  true;
351
     * ```
352
     * ```typescript
353
     * let isFocused = this.checkbox.focused;
354
     * ```
355
     */
356
    @HostBinding('class.igx-checkbox--focused')
357
    public focused = false;
8,189✔
358

359
    /**
360
     * Sets/gets the checkbox indeterminate visual state.
361
     * Default value is `false`;
362
     *
363
     * @example
364
     * ```html
365
     * <igx-checkbox [indeterminate]="true"></igx-checkbox>
366
     * ```
367
     * ```typescript
368
     * let isIndeterminate = this.checkbox.indeterminate;
369
     * ```
370
     */
371
    @HostBinding('class.igx-checkbox--indeterminate')
372
    @Input({ transform: booleanAttribute })
373
    public indeterminate = false;
8,189✔
374

375
    /**
376
     * Sets/gets whether the checkbox is checked.
377
     * Default value is `false`.
378
     *
379
     * @example
380
     * ```html
381
     * <igx-checkbox [checked]="true"></igx-checkbox>
382
     * ```
383
     * ```typescript
384
     * let isChecked =  this.checkbox.checked;
385
     * ```
386
     */
387
    @HostBinding('class.igx-checkbox--checked')
388
    @Input({ transform: booleanAttribute })
389
    public get checked() {
390
        return this._checked;
174,203✔
391
    }
392
    public set checked(value: boolean) {
393
        if (this._checked !== value) {
10,368✔
394
            this._checked = value;
4,048✔
395
            this._onChangeCallback(this._checked);
4,048✔
396
        }
397
    }
398

399
    /**
400
     * Sets/gets whether the checkbox is disabled.
401
     * Default value is `false`.
402
     *
403
     * @example
404
     * ```html
405
     * <igx-checkbox disabled></igx-checkbox>
406
     * ```
407
     * ```typescript
408
     * let isDisabled = this.checkbox.disabled;
409
     * ```
410
     */
411
    @HostBinding('class.igx-checkbox--disabled')
412
    @Input({ transform: booleanAttribute })
413
    public disabled = false;
8,189✔
414

415
    /**
416
     * Sets/gets whether the checkbox is invalid.
417
     * Default value is `false`.
418
     *
419
     * @example
420
     * ```html
421
     * <igx-checkbox invalid></igx-checkbox>
422
     * ```
423
     * ```typescript
424
     * let isInvalid = this.checkbox.invalid;
425
     * ```
426
     */
427
    @HostBinding('class.igx-checkbox--invalid')
428
    @Input({ transform: booleanAttribute })
429
    public invalid = false
8,189✔
430

431
    /**
432
     * Sets/gets whether the checkbox is readonly.
433
     * Default value is `false`.
434
     *
435
     * @example
436
     * ```html
437
     * <igx-checkbox [readonly]="true"></igx-checkbox>
438
     * ```
439
     * ```typescript
440
     * let readonly = this.checkbox.readonly;
441
     * ```
442
     */
443
    @Input({ transform: booleanAttribute })
444
    public readonly = false;
8,189✔
445

446
    /**
447
     * Sets/gets whether the checkbox should disable all css transitions.
448
     * Default value is `false`.
449
     *
450
     * @example
451
     * ```html
452
     * <igx-checkbox [disableTransitions]="true"></igx-checkbox>
453
     * ```
454
     * ```typescript
455
     * let disableTransitions = this.checkbox.disableTransitions;
456
     * ```
457
     */
458
    @HostBinding('class.igx-checkbox--plain')
459
    @Input({ transform: booleanAttribute })
460
    public disableTransitions = false;
8,189✔
461

462
    /**
463
     * @hidden
464
     * @internal
465
     */
466
    public inputId = `${this.id}-input`;
8,189✔
467

468
    /**
469
     * @hidden
470
     */
471
    protected _onChangeCallback: (_: any) => void = noop;
8,189✔
472

473
    /**
474
     * @hidden
475
     */
476
    private _onTouchedCallback: () => void = noop;
8,189✔
477

478
    /**
479
     * @hidden
480
     * @internal
481
     */
482
    protected _checked = false;
8,189✔
483

484
    /**
485
     * @hidden
486
     * @internal
487
     */
488
    protected theme: IgxTheme;
489

490
    /**
491
     * @hidden
492
     * @internal
493
     */
494
    private _required = false;
8,189✔
495
    private elRef = inject(ElementRef);
8,189✔
496
    private destroyRef = inject(DestroyRef);
8,189✔
497

498
    constructor(
499
        protected cdr: ChangeDetectorRef,
8,189✔
500
        protected renderer: Renderer2,
8,189✔
501
        @Inject(THEME_TOKEN)
502
        protected themeToken: ThemeToken,
8,189✔
503
        @Optional() @Self() public ngControl: NgControl,
8,189✔
504
    ) {
505
        if (this.ngControl !== null) {
8,189✔
506
            this.ngControl.valueAccessor = this;
44✔
507
        }
508

509
        this.theme = this.themeToken.theme;
8,189✔
510

511
        const { unsubscribe } = this.themeToken.onChange((theme) => {
8,189✔
512
            if (this.theme !== theme) {
8,189!
NEW
513
                this.theme = theme;
×
NEW
514
                this.cdr.detectChanges();
×
515
            }
516
        });
517

518
        this.destroyRef.onDestroy(() => unsubscribe);
8,189✔
519
    }
520

521
    private setComponentTheme() {
522
        if(!this.themeToken.preferToken) {
8,187✔
523
            const theme = getComponentTheme(this.elRef.nativeElement);
8,187✔
524

525
            if (theme && theme !== this.theme) {
8,187!
NEW
526
                this.theme = theme;
×
NEW
527
                this.cdr.markForCheck();
×
528
            }
529
        }
530
    }
531

532
    /**
533
     * @hidden
534
     * @internal
535
    */
536
    public ngAfterViewInit() {
537
        if (this.ngControl) {
8,187✔
538
            this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.updateValidityState.bind(this));
44✔
539

540
            if (this.ngControl.control.validator || this.ngControl.control.asyncValidator) {
44✔
541
                this._required = this.ngControl?.control?.hasValidator(Validators.required);
9✔
542
                this.cdr.detectChanges();
9✔
543
            }
544
        }
545

546
        this.setComponentTheme();
8,187✔
547
    }
548

549
    /** @hidden @internal */
550
    @HostListener('keyup', ['$event'])
551
    public onKeyUp(event: KeyboardEvent) {
552
        event.stopPropagation();
14✔
553
        this.focused = true;
14✔
554
    }
555

556
    /** @hidden @internal */
557
    @HostListener('click', ['$event'])
558
    public _onCheckboxClick(event: PointerEvent | MouseEvent) {
559
        // Since the original checkbox is hidden and the label
560
        // is used for styling and to change the checked state of the checkbox,
561
        // we need to prevent the checkbox click event from bubbling up
562
        // as it gets triggered on label click
563
        // NOTE: The above is no longer valid, as the native checkbox is not labeled
564
        // by the SVG anymore.
565
        if (this.disabled || this.readonly) {
152✔
566
            // readonly prevents the component from changing state (see toggle() method).
567
            // However, the native checkbox can still be activated through user interaction (focus + space, label click)
568
            // Prevent the native change so the input remains in sync
569
            event.preventDefault();
71✔
570
            return;
71✔
571
        }
572

573
        this.nativeInput.nativeElement.focus();
81✔
574

575
        this.indeterminate = false;
81✔
576
        this.checked = !this.checked;
81✔
577
        this.updateValidityState();
81✔
578

579
        // K.D. March 23, 2021 Emitting on click and not on the setter because otherwise every component
580
        // bound on change would have to perform self checks for weather the value has changed because
581
        // of the initial set on initialization
582
        this.change.emit({ checked: this.checked, value: this.value, owner: this });
81✔
583
    }
584

585
    /**
586
     * @hidden
587
     * @internal
588
     */
589
    public get ariaChecked() {
590
        if (this.indeterminate) {
58,075✔
591
            return 'mixed';
1,046✔
592
        } else {
593
            return this.checked;
57,029✔
594
        }
595
    }
596

597
    /** @hidden @internal */
598
    public _onCheckboxChange(event: Event) {
599
        // We have to stop the original checkbox change event
600
        // from bubbling up since we emit our own change event
601
        event.stopPropagation();
60✔
602
    }
603

604
    /** @hidden @internal */
605
    @HostListener('blur')
606
    public onBlur() {
607
        this.focused = false;
51✔
608
        this._onTouchedCallback();
51✔
609
        this.updateValidityState();
51✔
610
    }
611

612
    /** @hidden @internal */
613
    public writeValue(value: boolean) {
614
        this._checked = value;
53✔
615
    }
616

617
    /** @hidden @internal */
618
    public get labelClass(): string {
619
        switch (this.labelPosition) {
58,435✔
620
            case LabelPosition.BEFORE:
621
                return `${this.cssClass}__label--before`;
6✔
622
            case LabelPosition.AFTER:
623
            default:
624
                return `${this.cssClass}__label`;
58,429✔
625
        }
626
    }
627

628
    /** @hidden @internal */
629
    public registerOnChange(fn: (_: any) => void) {
630
        this._onChangeCallback = fn;
66✔
631
    }
632

633
    /** @hidden @internal */
634
    public registerOnTouched(fn: () => void) {
635
        this._onTouchedCallback = fn;
80✔
636
    }
637

638
    /** @hidden @internal */
639
    public setDisabledState(isDisabled: boolean) {
640
        this.disabled = isDisabled;
45✔
641
    }
642

643
    /** @hidden @internal */
644
    public getEditElement() {
645
        return this.nativeInput.nativeElement;
26✔
646
    }
647

648
    /**
649
     * @hidden
650
     * @internal
651
     */
652
    protected updateValidityState() {
653
        if (this.ngControl) {
187✔
654
            if (!this.disabled && !this.readonly &&
77✔
655
                (this.ngControl.control.touched || this.ngControl.control.dirty)) {
656
                // the control is not disabled and is touched or dirty
657
                this.invalid = this.ngControl.invalid;
42✔
658
            } else {
659
                //  if the control is untouched, pristine, or disabled, its state is initial. This is when the user did not interact
660
                //  with the checkbox or when the form/control is reset
661
                this.invalid = false;
35✔
662
            }
663
        } else {
664
            this.checkNativeValidity();
110✔
665
        }
666
    }
667

668
    /**
669
     * A function to assign a native validity property of a checkbox.
670
     * This should be used when there's no ngControl
671
     *
672
     * @hidden
673
     * @internal
674
     */
675
    private checkNativeValidity() {
676
        if (!this.disabled && this._required && !this.checked && !this.readonly) {
110✔
677
            this.invalid = true;
8✔
678
        } else {
679
            this.invalid = false;
102✔
680
        }
681
    }
682
}
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