Skip to content

Commit

Permalink
feat: add support for nested bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
anjmao committed Feb 2, 2018
1 parent 7c5748e commit 13491a0
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 44 deletions.
31 changes: 17 additions & 14 deletions demo/app/examples/bindings.component.ts
Expand Up @@ -7,36 +7,36 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
<label>Bind to default <b>label</b>, <b>object</b> bindings</label>
---html,true
<ng-select [items]="defaultBindingsList"
[(ngModel)]="selectedCity2">
[(ngModel)]="selectedCity">
</ng-select>
---
<p>
Selected city object: {{selectedCity2 | json}}
Selected city object: {{selectedCity | json}}
</p>
<hr>
<label>Bind label to nested custom property</label>
---html,true
<ng-select [items]="countries"
bindLabel="description.name"
bindLabel="nested.name"
bindValue="nested.countryId"
placeholder="Select value"
[clearable]="false"
[(ngModel)]="selectedCity">
[(ngModel)]="selectedCountryId">
</ng-select>
---
<p>
Selected city object: {{selectedCity | json}}
Selected country ID: {{selectedCountryId}}
</p>
<hr>
<label>Bind label and model to custom properties</label>
---html,true
<ng-select [items]="cities"
bindLabel="name"
bindValue="id"
[(ngModel)]="selectedCityId2">
[(ngModel)]="selectedCityId">
</ng-select>
---
<p>
Selected city ID: {{selectedCityId2 | json}}
Selected city ID: {{selectedCityId | json}}
</p>
`
})
Expand All @@ -55,16 +55,19 @@ export class SelectBindingsComponent {
];

countries = [
{ id: 1, description: { name: 'Lithuania' } },
{ id: 2, description: { name: 'USA' } },
{ id: 3, description: { name: 'Australia' } }
{ id: 1, nested: { countryId: 'L', name: 'Lithuania' } },
{ id: 2, nested: { countryId: 'U', name: 'USA' } },
{ id: 3, nested: { countryId: 'A', name: 'Australia' } }
];

selectedCity: any;
selectedCity2: number = null;
selectedCityId2: number = null;
selectedCountryId: string = null;
selectedCity = null;
selectedCityId: number = null;

ngOnInit() {
this.selectedCountryId = this.countries[0].nested.countryId;
this.selectedCity = this.defaultBindingsList[0];
this.selectedCityId = this.cities[0].id;
}
}

2 changes: 1 addition & 1 deletion src/ng-select/items-list.ts
Expand Up @@ -49,7 +49,7 @@ export class ItemsList {

findItem(value: any): NgOption {
if (this._ngSelect.bindValue) {
return this._items.find(item => item.value[this._ngSelect.bindValue] === value);
return this._items.find(item => this.resolveNested(item.value, this._ngSelect.bindValue) === value);
}
const index = this._items.findIndex(x => x.value === value);
return index > -1 ? this._items[index] :
Expand Down
37 changes: 31 additions & 6 deletions src/ng-select/ng-select.component.spec.ts
Expand Up @@ -263,7 +263,7 @@ describe('NgSelectComponent', function () {
NgSelectCustomBindingsTestCmp,
`<ng-select [items]="countries"
bindLabel="description.name"
[(ngModel)]="country">
[(ngModel)]="selectedCountry">
</ng-select>`);

selectOption(fixture, KeyCode.ArrowDown, 1);
Expand All @@ -273,14 +273,39 @@ describe('NgSelectComponent', function () {
value: fixture.componentInstance.countries[1]
})]);

fixture.componentInstance.country = fixture.componentInstance.countries[0];
fixture.componentInstance.selectedCountry = fixture.componentInstance.countries[0];
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
label: 'Lithuania',
value: fixture.componentInstance.countries[0]
})]);
}));

it('bind to nested value property', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectCustomBindingsTestCmp,
`<ng-select [items]="countries"
bindLabel="description.name"
bindValue="description.id"
[(ngModel)]="selectedCountry">
</ng-select>`);

selectOption(fixture, KeyCode.ArrowDown, 1);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCountry).toEqual('b');

fixture.componentInstance.selectedCountry = fixture.componentInstance.countries[2].description.id;
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.select.selectedItems).toEqual([jasmine.objectContaining({
label: 'Australia',
value: fixture.componentInstance.countries[2]
})]);

selectOption(fixture, KeyCode.ArrowUp, 1);
tickAndDetectChanges(fixture);
expect(fixture.componentInstance.selectedCountry).toEqual('b');
}));

it('bind to simple array', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectSimpleCmp,
Expand Down Expand Up @@ -1516,16 +1541,16 @@ class NgSelectSelectedObjectByRefCmp {
class NgSelectCustomBindingsTestCmp {
@ViewChild(NgSelectComponent) select: NgSelectComponent;
selectedCityId: number;
country: any;
selectedCountry: any;
cities = [
{ id: 1, name: 'Vilnius' },
{ id: 2, name: 'Kaunas' },
{ id: 3, name: 'Pabrade' }
];
countries = [
{ id: 1, description: { name: 'Lithuania' } },
{ id: 2, description: { name: 'USA' } },
{ id: 3, description: { name: 'Australia' } }
{ id: 1, description: { name: 'Lithuania', id: 'a' } },
{ id: 2, description: { name: 'USA', id: 'b' } },
{ id: 3, description: { name: 'Australia', id: 'c' } }
];
}

Expand Down
43 changes: 20 additions & 23 deletions src/ng-select/ng-select.component.ts
Expand Up @@ -232,13 +232,12 @@ export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, AfterVie
this.clearEvent.emit();
}

// TODO: make private
clearModel() {
if (!this.clearable) {
return;
}
this.itemsList.clearSelected();
this._notifyModelChanged();
this._updateNgModel();
}

writeValue(value: any | any[]): void {
Expand Down Expand Up @@ -309,7 +308,7 @@ export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, AfterVie
if (!item.selected) {
this.itemsList.select(item);
this._clearSearch();
this._updateModel();
this._updateNgModel();
this.addEvent.emit(item.value);
}

Expand All @@ -320,7 +319,7 @@ export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, AfterVie

unselect(item: NgOption) {
this.itemsList.unselect(item);
this._updateModel();
this._updateNgModel();
this.removeEvent.emit(item);
}

Expand Down Expand Up @@ -559,8 +558,22 @@ export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, AfterVie
}
}

private _updateModel() {
this._notifyModelChanged();
private _updateNgModel() {
let ngModel = this._value;
if (!this._isDefined(ngModel)) {
this._onChange(null);
} else if (this.bindValue) {
if (Array.isArray(ngModel)) {
ngModel = ngModel.map(option => this.itemsList.resolveNested(option, this.bindValue))
} else {
ngModel = this.itemsList.resolveNested(ngModel, this.bindValue);
}
this._onChange(ngModel);
} else {
this._onChange(ngModel);
}
this._ngModel = ngModel;
this.changeEvent.emit(this._value);
this.changeDetectorRef.markForCheck();
}

Expand Down Expand Up @@ -646,28 +659,12 @@ export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, AfterVie

if (this.multiple) {
this.itemsList.unselectLast();
this._updateModel();
this._updateNgModel();
} else {
this.clearModel();
}
}

private _notifyModelChanged() {
let ngModel = this._value;
if (!this._isDefined(ngModel)) {
this._onChange(null);
} else if (this.bindValue) {
ngModel = Array.isArray(ngModel) ?
ngModel.map(option => option[this.bindValue]) :
ngModel[this.bindValue];
this._onChange(ngModel);
} else {
this._onChange(ngModel);
}
this._ngModel = ngModel;
this.changeEvent.emit(this._value);
}

private _getDropdownMenu() {
if (!this.isOpen || !this.dropdownList) {
return null;
Expand Down

0 comments on commit 13491a0

Please sign in to comment.