Skip to content

Commit 2213cfc

Browse files
authored
Merge pull request #6478 from IgniteUI/dpetev/select-control-touched
Select - properly update FormControl touched
2 parents 0f02748 + e68dd04 commit 2213cfc

File tree

5 files changed

+124
-39
lines changed

5 files changed

+124
-39
lines changed

projects/igniteui-angular/src/lib/select/select.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
[attr.aria-owns]="this.listId"
1717
[attr.aria-activedescendant]="!this.collapsed ? this.focusedItem?.id : null"
1818
(blur)="onBlur()"
19+
(focus)="onFocus()"
1920
/>
2021
<ng-container ngProjectAs="igx-suffix">
2122
<ng-content select="igx-suffix,[igxSuffix]"></ng-content>

projects/igniteui-angular/src/lib/select/select.component.spec.ts

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { IgxInputState } from './../directives/input/input.directive';
22
import { Component, ViewChild, DebugElement, OnInit, ViewChildren, QueryList } from '@angular/core';
33
import { async, TestBed, tick, fakeAsync } from '@angular/core/testing';
4-
import { FormsModule, FormGroup, FormBuilder, FormControl, Validators, ReactiveFormsModule, NgForm } from '@angular/forms';
4+
import { FormsModule, FormGroup, FormBuilder, FormControl, Validators, ReactiveFormsModule, NgForm, NgControl } from '@angular/forms';
55
import { By } from '@angular/platform-browser';
6-
import { IgxDropDownModule } from '../drop-down/index';
6+
import { IgxDropDownModule, IgxDropDownItemComponent } from '../drop-down/index';
77
import { IgxIconModule } from '../icon/index';
88
import { IgxInputGroupModule } from '../input-group/index';
99
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -612,6 +612,24 @@ describe('igxSelect', () => {
612612
expect(inputGroupWithRequiredAsterisk).toBeDefined();
613613
}));
614614

615+
it('Should have correctly bound focus and blur handlers', () => {
616+
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
617+
fix.detectChanges();
618+
select = fix.componentInstance.select;
619+
const input = fix.debugElement.query(By.css(`.${CSS_CLASS_INPUT}`));
620+
621+
spyOn(select, 'onFocus');
622+
spyOn(select, 'onBlur');
623+
624+
input.triggerEventHandler('focus', {});
625+
expect(select.onFocus).toHaveBeenCalled();
626+
expect(select.onFocus).toHaveBeenCalledWith();
627+
628+
input.triggerEventHandler('blur', {});
629+
expect(select.onBlur).toHaveBeenCalled();
630+
expect(select.onFocus).toHaveBeenCalledWith();
631+
});
632+
615633
// Bug #6025 Select does not disable in reactive form
616634
it('Should disable when form is disabled', fakeAsync(() => {
617635
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
@@ -2493,6 +2511,58 @@ describe('igxSelect', () => {
24932511
expect(selectCDR.value).toBe('ID');
24942512
});
24952513
});
2514+
});
2515+
2516+
describe('igxSelect ControlValueAccessor Unit', () => {
2517+
let select: IgxSelectComponent;
2518+
it('Should correctly implement interface methods', () => {
2519+
const mockSelection = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'clear', 'first_item']);
2520+
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['detectChanges']);
2521+
const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']);
2522+
const mockInjector = jasmine.createSpyObj('Injector', {
2523+
'get': mockNgControl
2524+
});
2525+
2526+
// init
2527+
select = new IgxSelectComponent(null, mockCdr, mockSelection, null, mockInjector);
2528+
select.ngOnInit();
2529+
select.registerOnChange(mockNgControl.registerOnChangeCb);
2530+
select.registerOnTouched(mockNgControl.registerOnTouchedCb);
2531+
expect(mockInjector.get).toHaveBeenCalledWith(NgControl, null);
2532+
2533+
// writeValue
2534+
expect(select.value).toBeUndefined();
2535+
select.writeValue('test');
2536+
expect(mockSelection.clear).toHaveBeenCalled();
2537+
expect(select.value).toBe('test');
2538+
2539+
// setDisabledState
2540+
select.setDisabledState(true);
2541+
expect(select.disabled).toBe(true);
2542+
select.setDisabledState(false);
2543+
expect(select.disabled).toBe(false);
2544+
2545+
// OnChange callback
2546+
const item = new IgxDropDownItemComponent(select, null, null, mockSelection);
2547+
item.value = 'itemValue';
2548+
select.selectItem(item);
2549+
expect(mockSelection.set).toHaveBeenCalledWith(select.id, new Set([item]));
2550+
expect(mockNgControl.registerOnChangeCb).toHaveBeenCalledWith('itemValue');
2551+
2552+
// OnTouched callback
2553+
select.onFocus();
2554+
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1);
2555+
2556+
select.input = {} as any;
2557+
spyOnProperty(select, 'collapsed').and.returnValue(true);
2558+
select.onBlur();
2559+
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(2);
2560+
});
2561+
2562+
it('Should correctly handle ngControl validity', () => {
2563+
pending('Convert existing form test here');
2564+
});
2565+
});
24962566

24972567
@Component({
24982568
template: `
@@ -2820,28 +2890,27 @@ class IgxSelectHeaderFooterComponent implements OnInit {
28202890
}
28212891
}
28222892

2823-
@Component({
2824-
template: `
2825-
<h4>*ngIf test select for 'expression changed...console Warning'</h4>
2826-
<div *ngIf="render">
2827-
<igx-select #selectCDR value="ID">
2828-
<label igxLabel>Column</label>
2829-
<igx-select-item *ngFor="let column of columns" [value]="column.field">
2830-
{{column.field}}
2831-
</igx-select-item>
2832-
</igx-select>
2833-
</div>
2834-
`
2835-
})
2836-
class IgxSelectCDRComponent {
2837-
@ViewChild('selectCDR', { read: IgxSelectComponent, static: false })
2838-
public select: IgxSelectComponent;
2839-
2840-
public render = true;
2841-
public columns: Array<any> = [
2842-
{ field: 'ID', type: 'string' },
2843-
{ field: 'CompanyName', type: 'string' },
2844-
{ field: 'ContactName', type: 'string' }
2845-
];
2846-
}
2847-
});
2893+
@Component({
2894+
template: `
2895+
<h4>*ngIf test select for 'expression changed...console Warning'</h4>
2896+
<div *ngIf="render">
2897+
<igx-select #selectCDR value="ID">
2898+
<label igxLabel>Column</label>
2899+
<igx-select-item *ngFor="let column of columns" [value]="column.field">
2900+
{{column.field}}
2901+
</igx-select-item>
2902+
</igx-select>
2903+
</div>
2904+
`
2905+
})
2906+
class IgxSelectCDRComponent {
2907+
@ViewChild('selectCDR', { read: IgxSelectComponent, static: false })
2908+
public select: IgxSelectComponent;
2909+
2910+
public render = true;
2911+
public columns: Array<any> = [
2912+
{ field: 'ID', type: 'string' },
2913+
{ field: 'CompanyName', type: 'string' },
2914+
{ field: 'ContactName', type: 'string' }
2915+
];
2916+
}

projects/igniteui-angular/src/lib/select/select.component.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,12 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
261261
super(elementRef, cdr, selection, _displayDensityOptions);
262262
}
263263

264+
//#region ControlValueAccessor
265+
264266
/** @hidden @internal */
265267
private _onChangeCallback: (_: any) => void = noop;
268+
/** @hidden @internal */
269+
private _onTouchedCallback: () => void = noop;
266270

267271
/** @hidden @internal */
268272
public writeValue = (value: any) => {
@@ -275,12 +279,15 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
275279
}
276280

277281
/** @hidden @internal */
278-
public registerOnTouched(fn: any): void { }
282+
public registerOnTouched(fn: any): void {
283+
this._onTouchedCallback = fn;
284+
}
279285

280286
/** @hidden @internal */
281287
public setDisabledState(isDisabled: boolean): void {
282288
this.disabled = isDisabled;
283289
}
290+
//#endregion
284291

285292
/** @hidden @internal */
286293
public getEditElement(): HTMLElement {
@@ -384,6 +391,7 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
384391

385392
/** @hidden @internal */
386393
public onBlur(): void {
394+
this._onTouchedCallback();
387395
if (this.ngControl && !this.ngControl.valid) {
388396
this.input.valid = IgxInputState.INVALID;
389397
} else {
@@ -394,6 +402,11 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
394402
}
395403
}
396404

405+
/** @hidden @internal */
406+
public onFocus(): void {
407+
this._onTouchedCallback();
408+
}
409+
397410
protected onStatusChanged() {
398411
if ((this.ngControl.control.touched || this.ngControl.control.dirty) &&
399412
(this.ngControl.control.validator || this.ngControl.control.asyncValidator)) {

src/app/select/select.sample.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ <h4 class="sample-title">Select with ngModel, set items OnInit</h4>
1212
[required]="true"
1313
[placeholder]="'Pick One'"
1414
[(ngModel)]="value"
15+
[ngModelOptions]="{updateOn: 'blur'}" #selectModel="ngModel"
16+
required
1517
(onOpening)="testOnOpening($event)"
1618
(onOpened)="testOnOpened()"
1719
(onClosing)="testOnClosing($event)"
@@ -37,12 +39,15 @@ <h4 class="sample-title">Select with ngModel, set items OnInit</h4>
3739
</div>
3840
</ng-template>
3941
</igx-select>
42+
<div>Model: {{selectModel.value}}</div>
4043

4144
<div>
4245
<h4>Display Density</h4>
43-
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === compact" (click)="setDensity(compact)">Compact</button>
44-
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === cosy" (click)="setDensity(cosy)">Cosy</button>
45-
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === comfortable" (click)="setDensity(comfortable)">Comfortable</button>
46+
<igx-buttongroup (onSelect)="setDensity($event)">
47+
<button igxButton value="compact">Compact</button>
48+
<button igxButton value="cosy">Cosy</button>
49+
<button igxButton value="comfortable">Comfortable</button>
50+
</igx-buttongroup>
4651
</div>
4752

4853
<h4 class="sample-title">Select - declare items in html template</h4>

src/app/select/select.sample.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
HorizontalAlignment, VerticalAlignment, scaleInTop, scaleOutBottom, ConnectedPositioningStrategy,
66
AbsoluteScrollStrategy,
77
IgxSelectComponent,
8-
DisplayDensity
8+
DisplayDensity,
9+
IButtonGroupEventArgs
910
} from 'igniteui-angular';
1011

1112
@Component({
@@ -23,10 +24,6 @@ export class SelectSampleComponent implements OnInit {
2324
@ViewChild('displayDensitySelect', { read: IgxSelectComponent, static: true })
2425
public selectDisplayDensity: IgxSelectComponent;
2526

26-
public comfortable = DisplayDensity.comfortable;
27-
public cosy = DisplayDensity.cosy;
28-
public compact = DisplayDensity.compact;
29-
3027
constructor(fb: FormBuilder) {
3128
this.reactiveForm = fb.group({
3229
'citiesSelect': ['', Validators.required]
@@ -64,7 +61,7 @@ export class SelectSampleComponent implements OnInit {
6461
public onSubmitReactive() { }
6562

6663
public selectBanana() {
67-
this.selectFruits.selectItem(this.selectFruits.items[3]);
64+
this.selectFruits.setSelectedItem(3);
6865
}
6966

7067
public setToNull() {
@@ -144,8 +141,8 @@ export class SelectSampleComponent implements OnInit {
144141
}
145142
}
146143

147-
setDensity(density: DisplayDensity) {
148-
this.selectDisplayDensity.displayDensity = density;
144+
setDensity(event: IButtonGroupEventArgs) {
145+
this.selectDisplayDensity.displayDensity = event.button.nativeElement.value;
149146
}
150147

151148
btnClick() {

0 commit comments

Comments
 (0)