Skip to content

Commit e05b38d

Browse files
RivaIvanovateodosiahChronosSF
authored
Select falsy values via ngModel - master (#12102)
* test(simple-combo): should select falsy values with "writeValue" method * fix(simple-combo): select falsy values with "writeValue" method * test(selectionService): should add items with falsy itemID except undefined * fix(selectionService): add items with falsy itemID except undefined * test(combos): should select falsy values except "undefined" * fix(combos): select falsy values except "undefined" * fix(combos): display and select "null" in dropdown * test(selectionService): removing an outdated test * fix(selectionService): removing throw error check * fix(combos): additional checks for nullish values * chore(combos): add comment for template check * chore(remoteNWindService): remove debugger * test(combos): should remove undefined from array of primitive data * fix(combos): remove undefined from array of primitive data * fix(combos): remove undefined in data setter * fix(simple-combo): show all items in dropdown * chore(combo-pipes): add comment for grouping pipe * test(simple-combo): should not select null, undefined, '' in template form * fix(simple-combo): not select null, undefined, '' in template form * fix(simple-combo): not select null, undefined, '' in reactive form Co-authored-by: Teodosia Hristodorova <[email protected]> Co-authored-by: Stamen Stoychev <[email protected]>
1 parent 33506ab commit e05b38d

File tree

12 files changed

+537
-46
lines changed

12 files changed

+537
-46
lines changed

projects/igniteui-angular/src/lib/combo/combo-dropdown.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export class IgxComboDropDownComponent extends IgxDropDownComponent implements I
113113
* @hidden
114114
*/
115115
public navigateFirst() {
116-
this.navigateItem(this.virtDir.igxForOf.findIndex(e => !e.isHeader));
116+
this.navigateItem(this.virtDir.igxForOf.findIndex(e => !e?.isHeader));
117117
this.combo.setActiveDescendant();
118118
}
119119

projects/igniteui-angular/src/lib/combo/combo.api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ export class IgxComboAPIService {
4141

4242
public set_selected_item(itemID: any, event?: Event): void {
4343
const selected = this.combo.isItemSelected(itemID);
44-
44+
if (itemID === undefined) {
45+
return;
46+
}
4547
if (!selected) {
4648
this.combo.select([itemID], false, event);
4749
} else {

projects/igniteui-angular/src/lib/combo/combo.common.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,13 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
307307
return this._data;
308308
}
309309
public set data(val: any[] | null) {
310-
this._data = (val) ? val : [];
310+
// igxFor directive ignores undefined values
311+
// if the combo uses simple data and filtering is applied
312+
// an error will occur due to the mismatch of the length of the data
313+
// this can occur during filtering for the igx-combo and
314+
// during filtering & selection for the igx-simple-combo
315+
// since the simple combo's input is both a container for the selection and a filter
316+
this._data = (val) ? val.filter(x => x !== undefined) : [];
311317
}
312318

313319
/**
@@ -1297,6 +1303,16 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
12971303
return Object.keys(this._remoteSelection).map(e => this._remoteSelection[e]).join(', ');
12981304
}
12991305

1306+
protected get required(): boolean {
1307+
if (this.ngControl && this.ngControl.control && this.ngControl.control.validator) {
1308+
// Run the validation with empty object to check if required is enabled.
1309+
const error = this.ngControl.control.validator({} as AbstractControl);
1310+
return error && error.required;
1311+
}
1312+
1313+
return false;
1314+
}
1315+
13001316
public abstract get filteredData(): any[] | null;
13011317
public abstract set filteredData(val: any[] | null);
13021318

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,15 @@
6060
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction
6161
| comboGrouping:groupKey:valueKey:groupSortingDirection;
6262
index as rowIndex; containerSize: itemsMaxHeight; scrollOrientation: 'vertical'; itemSize: itemHeight"
63-
[value]="item" [isHeader]="item.isHeader" [index]="rowIndex" [role]="item.isHeader? 'group' : 'option'">
64-
<ng-container *ngIf="item.isHeader">
63+
[value]="item" [isHeader]="item?.isHeader" [index]="rowIndex" [role]="item?.isHeader? 'group' : 'option'">
64+
<ng-container *ngIf="item?.isHeader">
6565
<ng-container
6666
*ngTemplateOutlet="headerItemTemplate ? headerItemTemplate : headerItemBase;
6767
context: {$implicit: item, data: data, valueKey: valueKey, groupKey: groupKey, displayKey: displayKey}">
6868
</ng-container>
6969
</ng-container>
70-
<ng-container *ngIf="!item.isHeader">
70+
<!-- if item is 'null' it should be displayed and !!(item?.isHeader) would resolve it to 'false' and not display it -->
71+
<ng-container *ngIf="!item?.isHeader">
7172
<ng-container #listItem
7273
*ngTemplateOutlet="template; context: {$implicit: item, data: data, valueKey: valueKey, displayKey: displayKey};">
7374
</ng-container>

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

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,14 @@ describe('igxCombo', () => {
12251225
const comboData = combo.data;
12261226
expect(comboData).toEqual(data);
12271227
});
1228+
it('should remove undefined from array of primitive data', () => {
1229+
fixture = TestBed.createComponent(IgxComboInContainerTestComponent);
1230+
fixture.detectChanges();
1231+
combo = fixture.componentInstance.combo;
1232+
combo.data = ['New York', 'Sofia', undefined, 'Istanbul','Paris'];
1233+
1234+
expect(combo.data).toEqual(['New York', 'Sofia', 'Istanbul','Paris']);
1235+
});
12281236
it('should bind combo data to array of objects', () => {
12291237
fixture = TestBed.createComponent(IgxComboSampleComponent);
12301238
fixture.detectChanges();
@@ -2087,16 +2095,16 @@ describe('igxCombo', () => {
20872095
expect(combo.selection.length).toEqual(0);
20882096
expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(0);
20892097
});
2090-
it('should select unique falsy item values', () => {
2098+
it('should select falsy values except "undefined"', () => {
20912099
combo.valueKey = 'value';
20922100
combo.displayKey = 'field';
20932101
combo.data = [
20942102
{ field: '0', value: 0 },
20952103
{ field: 'false', value: false },
20962104
{ field: '', value: '' },
2097-
{ field: 'undefined', value: undefined },
20982105
{ field: 'null', value: null },
20992106
{ field: 'NaN', value: NaN },
2107+
{ field: 'undefined', value: undefined },
21002108
];
21012109

21022110
combo.open();
@@ -2136,8 +2144,8 @@ describe('igxCombo', () => {
21362144

21372145
item4.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
21382146
fixture.detectChanges();
2139-
expect(combo.value).toBe('0, false, , undefined');
2140-
expect(combo.selection).toEqual([ 0, false, '', undefined ]);
2147+
expect(combo.value).toBe('0, false, , null');
2148+
expect(combo.selection).toEqual([ 0, false, '', null ]);
21412149

21422150
combo.open();
21432151
fixture.detectChanges();
@@ -2146,8 +2154,8 @@ describe('igxCombo', () => {
21462154

21472155
item5.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
21482156
fixture.detectChanges();
2149-
expect(combo.value).toBe('0, false, , undefined, null');
2150-
expect(combo.selection).toEqual([ 0, false, '', undefined, null ]);
2157+
expect(combo.value).toBe('0, false, , null, NaN');
2158+
expect(combo.selection).toEqual([ 0, false, '', null, NaN ]);
21512159

21522160
combo.open();
21532161
fixture.detectChanges();
@@ -2156,8 +2164,45 @@ describe('igxCombo', () => {
21562164

21572165
item6.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
21582166
fixture.detectChanges();
2159-
expect(combo.value).toBe('0, false, , undefined, null, NaN');
2160-
expect(combo.selection).toEqual([ 0, false, '', undefined, null, NaN ]);
2167+
expect(combo.value).toBe('0, false, , null, NaN');
2168+
expect(combo.selection).toEqual([ 0, false, '', null, NaN ]);
2169+
});
2170+
it('should select falsy values except "undefined" with "writeValue" method', () => {
2171+
combo.valueKey = 'value';
2172+
combo.displayKey = 'field';
2173+
combo.data = [
2174+
{ field: '0', value: 0 },
2175+
{ field: 'false', value: false },
2176+
{ field: 'empty', value: '' },
2177+
{ field: 'null', value: null },
2178+
{ field: 'NaN', value: NaN },
2179+
{ field: 'undefined', value: undefined },
2180+
];
2181+
2182+
combo.writeValue([0]);
2183+
expect(combo.selection).toEqual([0]);
2184+
expect(combo.value).toBe('0');
2185+
2186+
combo.writeValue([false]);
2187+
expect(combo.selection).toEqual([false]);
2188+
expect(combo.value).toBe('false');
2189+
2190+
combo.writeValue(['']);
2191+
expect(combo.selection).toEqual(['']);
2192+
expect(combo.value).toBe('empty');
2193+
2194+
combo.writeValue([null]);
2195+
expect(combo.selection).toEqual([null]);
2196+
expect(combo.value).toBe('null');
2197+
2198+
combo.writeValue([NaN]);
2199+
expect(combo.selection).toEqual([NaN]);
2200+
expect(combo.value).toBe('NaN');
2201+
2202+
// should not select undefined
2203+
combo.writeValue([undefined]);
2204+
expect(combo.selection).toEqual([]);
2205+
expect(combo.value).toBe('');
21612206
});
21622207
it('should prevent selection when selectionChanging is cancelled', () => {
21632208
spyOn(combo.selectionChanging, 'emit').and.callFake((event: IComboSelectionChangingEventArgs) => event.cancel = true);

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
235235
* @hidden @internal
236236
*/
237237
public writeValue(value: any[]): void {
238-
const selection = Array.isArray(value) ? value : [];
238+
const selection = Array.isArray(value) ? value.filter(x => x !== undefined) : [];
239239
const oldSelection = this.selection;
240240
this.selectionService.select_items(this.id, selection, true);
241241
this.cdr.markForCheck();
@@ -355,6 +355,9 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
355355
* ```
356356
*/
357357
public setSelectedItem(itemID: any, select = true, event?: Event): void {
358+
if (itemID === undefined) {
359+
return;
360+
}
358361
if (select) {
359362
this.select([itemID], false, event);
360363
} else {

projects/igniteui-angular/src/lib/combo/combo.pipes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class IgxComboGroupingPipe implements PipeTransform {
3131
constructor(@Inject(IGX_COMBO_COMPONENT) public combo: IgxComboBase) { }
3232

3333
public transform(collection: any[], groupKey: any, valueKey: any, sortingDirection: SortingDirection) {
34+
// TODO: should filteredData be changed here?
3435
this.combo.filteredData = collection;
3536
if ((!groupKey && groupKey !== 0) || !collection.length) {
3637
return collection;
@@ -74,7 +75,7 @@ function defaultFilterFunction (collection: any[], searchValue: any, filteringOp
7475
e[filteringOptions.filteringKey]?.toString().toLowerCase().includes(searchTerm));
7576
} else {
7677
return collection.filter(e => filteringOptions.caseSensitive ?
77-
e.includes(searchTerm) :
78-
e.toString().toLowerCase().includes(searchTerm));
78+
e?.includes(searchTerm) :
79+
e?.toString().toLowerCase().includes(searchTerm));
7980
}
8081
}

projects/igniteui-angular/src/lib/core/selection.spec.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,10 @@ describe('IgxSelectionAPIService', () => {
2323
const selection3 = service.add_item(componentId, null);
2424
expect(selection3.has(null)).toBe(true);
2525

26-
const selection4 = service.add_item(componentId, undefined);
27-
expect(selection4.has(undefined)).toBe(true);
26+
const selection4 = service.add_item(componentId, '');
27+
expect(selection4.has('')).toBe(true);
2828

29-
const selection5 = service.add_item(componentId, '');
30-
expect(selection5.has('')).toBe(true);
31-
32-
const selection6 = service.add_item(componentId, NaN);
33-
expect(selection6.has(NaN)).toBe(true);
29+
const selection5 = service.add_item(componentId, NaN);
30+
expect(selection5.has(NaN)).toBe(true);
3431
});
3532
});

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,20 @@
5555
[tabindex]="dropdown.collapsed ? -1 : 0" [attr.id]="dropdown.id"
5656
[attr.aria-activedescendant]="this.activeDescendant"
5757
(focus)="dropdown.onFocus()" (keydown)="handleItemKeyDown($event)">
58-
<igx-combo-item [role]="item.isHeader? 'group' : 'option'" [singleMode]="true"
58+
<igx-combo-item [role]="item?.isHeader? 'group' : 'option'" [singleMode]="true"
5959
[itemHeight]="itemHeight" (click)="handleItemClick()" *igxFor="let item of data
6060
| comboFiltering:filterValue:displayKey:filteringOptions:filterFunction
6161
| comboGrouping:groupKey:valueKey:groupSortingDirection;
6262
index as rowIndex; containerSize: itemsMaxHeight; scrollOrientation: 'vertical'; itemSize: itemHeight"
63-
[value]="item" [isHeader]="item.isHeader" [index]="rowIndex">
64-
<ng-container *ngIf="item.isHeader">
63+
[value]="item" [isHeader]="item?.isHeader" [index]="rowIndex">
64+
<ng-container *ngIf="item?.isHeader">
6565
<ng-container
6666
*ngTemplateOutlet="headerItemTemplate ? headerItemTemplate : headerItemBase;
6767
context: {$implicit: item, data: data, valueKey: valueKey, groupKey: groupKey, displayKey: displayKey}">
6868
</ng-container>
6969
</ng-container>
70-
<ng-container *ngIf="!item.isHeader">
70+
<!-- if item is 'null' it should be displayed and !!(item?.isHeader) would resolve it to 'false' and not display it -->
71+
<ng-container *ngIf="!item?.isHeader">
7172
<ng-container #listItem
7273
*ngTemplateOutlet="template; context: {$implicit: item, data: data, valueKey: valueKey, displayKey: displayKey};">
7374
</ng-container>

0 commit comments

Comments
 (0)