Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix(select): add basic track by and select as support
Browse files Browse the repository at this point in the history
Instead of throwing an error when using "track by" and "select as" expressions,
ngOptions will assume that the track by expression is valid, and will use it to
compare values.

Closes #6564
  • Loading branch information
jeffbcross committed Oct 10, 2014
1 parent 6e4955a commit addfff3
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 21 deletions.
3 changes: 3 additions & 0 deletions docs/content/error/ngOptions/trkslct.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
@fullName Comprehension expression cannot contain both `select as` and `track by` expressions.
@description

NOTE: This error was introduced in 1.3.0-rc.5, and was removed for 1.3.0-rc.6 in order to
not break existing apps.

This error occurs when 'ngOptions' is passed a comprehension expression that contains both a
`select as` expression and a `track by` expression. These two expressions are fundamentally
incompatible.
Expand Down
18 changes: 6 additions & 12 deletions src/ng/directive/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,13 +363,6 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
//re-usable object to represent option's locals
locals = {};

if (trackFn && selectAsFn) {
throw ngOptionsMinErr('trkslct',
"Comprehension expression cannot contain both selectAs '{0}' " +
"and trackBy '{1}' expressions.",
selectAs, track);
}

if (nullOption) {
// compile the element since there might be bindings in it
$compile(nullOption)(scope);
Expand Down Expand Up @@ -460,7 +453,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
function createIsSelectedFn(viewValue) {
var selectedSet;
if (multiple) {
if (!selectAs && trackFn && isArray(viewValue)) {
if (trackFn && isArray(viewValue)) {

selectedSet = new HashMap([]);
for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
Expand All @@ -470,15 +463,16 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
} else {
selectedSet = new HashMap(viewValue);
}
} else if (!selectAsFn && trackFn) {
} else if (trackFn) {
viewValue = callExpression(trackFn, null, viewValue);
}

return function isSelected(key, value) {
var compareValueFn;
if (selectAsFn) {
compareValueFn = selectAsFn;
} else if (trackFn) {
if (trackFn) {
compareValueFn = trackFn;
} else if (selectAsFn) {
compareValueFn = selectAsFn;
} else {
compareValueFn = valueFn;
}
Expand Down
176 changes: 167 additions & 9 deletions test/ng/directive/selectSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -817,22 +817,180 @@ describe('select', function() {
});


/**
* This behavior is broken and should probably be cleaned up later as track by and select as
* aren't compatible.
*/
describe('selectAs+trackBy expression', function() {
beforeEach(function() {
scope.arr = [{id: 10, label: 'ten'}, {id:'20', label: 'twenty'}];
scope.obj = {'10': {score: 10, label: 'ten'}, '20': {score: 20, label: 'twenty'}};
scope.arr = [{subItem: {label: 'ten', id: 10}}, {subItem: {label: 'twenty', id: 20}}];
scope.obj = {'10': {subItem: {id: 10, label: 'ten'}}, '20': {subItem: {id: 20, label: 'twenty'}}};
});


it('should throw a helpful minerr', function() {
expect(function() {
it('It should use the "value" variable to represent items in the array as well as for the ' +
'selected values in track by expression (single&array)', function() {
createSelect({
'ng-model': 'selected',
'ng-options': 'item.subItem as item.subItem.label for item in arr track by (item.id || item.subItem.id)'
});

// First test model -> view

scope.$apply(function() {
scope.selected = scope.arr[0].subItem;
});
expect(element.val()).toEqual('0');

scope.$apply(function() {
scope.selected = scope.arr[1].subItem;
});
expect(element.val()).toEqual('1');

// Now test view -> model

element.val('0');
browserTrigger(element, 'change');
expect(scope.selected).toBe(scope.arr[0].subItem);

// Now reload the array
scope.$apply(function() {
scope.arr = [{
subItem: {label: 'new ten', id: 10}
},{
subItem: {label: 'new twenty', id: 20}
}];
});
expect(element.val()).toBe('0');
expect(scope.selected.id).toBe(10);
});


it('It should use the "value" variable to represent items in the array as well as for the ' +
'selected values in track by expression (multiple&array)', function() {
createSelect({
'ng-model': 'selected',
'multiple': true,
'ng-options': 'item.subItem as item.subItem.label for item in arr track by (item.id || item.subItem.id)'
});

// First test model -> view

scope.$apply(function() {
scope.selected = [scope.arr[0].subItem];
});
expect(element.val()).toEqual(['0']);

scope.$apply(function() {
scope.selected = [scope.arr[1].subItem];
});
expect(element.val()).toEqual(['1']);

// Now test view -> model

element.find('option')[0].selected = true;
element.find('option')[1].selected = false;
browserTrigger(element, 'change');
expect(scope.selected).toEqual([scope.arr[0].subItem]);

// Now reload the array
scope.$apply(function() {
scope.arr = [{
subItem: {label: 'new ten', id: 10}
},{
subItem: {label: 'new twenty', id: 20}
}];
});
expect(element.val()).toEqual(['0']);
expect(scope.selected[0].id).toEqual(10);
expect(scope.selected.length).toBe(1);
});

createSelect({
'ng-model': 'selected',
'ng-options': 'item.id as item.name for item in values track by item.id'
});

}).toThrowMinErr('ngOptions', 'trkslct', "Comprehension expression cannot contain both selectAs 'item.id' and trackBy 'item.id' expressions.");
it('It should use the "value" variable to represent items in the array as well as for the ' +
'selected values in track by expression (multiple&object)', function() {
createSelect({
'ng-model': 'selected',
'multiple': true,
'ng-options': 'val.subItem as val.subItem.label for (key, val) in obj track by (val.id || val.subItem.id)'
});

// First test model -> view

scope.$apply(function() {
scope.selected = [scope.obj['10'].subItem];
});
expect(element.val()).toEqual(['10']);


scope.$apply(function() {
scope.selected = [scope.obj['10'].subItem];
});
expect(element.val()).toEqual(['10']);

// Now test view -> model

element.find('option')[0].selected = true;
element.find('option')[1].selected = false;
browserTrigger(element, 'change');
expect(scope.selected).toEqual([scope.obj['10'].subItem]);

// Now reload the object
scope.$apply(function() {
scope.obj = {
'10': {
subItem: {label: 'new ten', id: 10}
},
'20': {
subItem: {label: 'new twenty', id: 20}
}
};
});
expect(element.val()).toEqual(['10']);
expect(scope.selected[0].id).toBe(10);
expect(scope.selected.length).toBe(1);
});


it('It should use the "value" variable to represent items in the array as well as for the ' +
'selected values in track by expression (single&object)', function() {
createSelect({
'ng-model': 'selected',
'ng-options': 'val.subItem as val.subItem.label for (key, val) in obj track by (val.id || val.subItem.id)'
});

// First test model -> view

scope.$apply(function() {
scope.selected = scope.obj['10'].subItem;
});
expect(element.val()).toEqual('10');


scope.$apply(function() {
scope.selected = scope.obj['10'].subItem;
});
expect(element.val()).toEqual('10');

// Now test view -> model

element.find('option')[0].selected = true;
browserTrigger(element, 'change');
expect(scope.selected).toEqual(scope.obj['10'].subItem);

// Now reload the object
scope.$apply(function() {
scope.obj = {
'10': {
subItem: {label: 'new ten', id: 10}
},
'20': {
subItem: {label: 'new twenty', id: 20}
}
};
});
expect(element.val()).toEqual('10');
expect(scope.selected.id).toBe(10);
});
});

Expand Down

0 comments on commit addfff3

Please sign in to comment.