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

refact(ngOptions): major refactoring... #10639

Closed
wants to merge 9 commits into from

Conversation

petebacondarwin
Copy link
Contributor

Major reworking of select and ngOptions:

  • The SelectController is now used as an abstraction for the select and ngOptions directives
    to override to get their desired behaviour
  • The select directive is completely oblivious to the ngOptions directive now - the ngOptions
    directive could be deleted without having to make any changes to the select directive.
  • Select related directives (single/multiple/ngOptions) can provide specific versions of
    SelectController.writeValue and SelectController.readValue, which are responsible for getting
    the $viewValue in or out of the actual <select> element and its <option> children.

BREAKING CHANGE:

When using ngOptions: the directive applies a surrogate key as the value of the <option> element.
This commit changes the actual string used as the surrogate key. We now store a string that is computed
by calling hashKey on the item in the options collection; previously it was the index or key of the
item in the collection.

Before you might have seen:

<select ng-model="x" ng-option="i in items">
  <option value="1">a</option>
  <option value="2">b</option>
  <option value="3">c</option>
  <option value="4">d</option>
</select>

Now it will be something like:

<select ng-model="x" ng-option="i in items">
  <option value="string:a">a</option>
  <option value="string:b">b</option>
  <option value="string:c">c</option>
  <option value="string:d">d</option>
</select>

If your application code relied on this value, which it shouldn't, then you will need to modify your
application to accommodate this. You may find that you can use the track by feaure of ngOptions
as this provides the ability to specify the key that is stored.

BREAKING CHANGE:

When iterating over an object's properties using the (key, value) in obj syntax the order of the elements used to be sorted alphabetically. This was an artificial attempt to create a deterministic ordering since browsers don't guarantee the order. But in practice this is not what people want and so this change iterates over properties in the order they are returned by Object.keys(obj), which is almost always the order in which the properties were defined.

Closes #8019
Closes #9714
Closes #10531

@lgalfaso
Copy link
Contributor

lgalfaso commented Jan 4, 2015

would it be possible to split this into two commits, one that move things around and another that make the modifications? It would make it a lot easier to review the patch. One PR is ok and so is the order of the commits

@petebacondarwin
Copy link
Contributor Author

Not really. The original file (select.js) is so interwoven it was not really feasible to extract the ngOptions bits without doing a lot of the refactoring. I'll see what I can do...

@petebacondarwin
Copy link
Contributor Author

I guess I could combine these two files into one and then split them in a subsequent commit?

@lgalfaso
Copy link
Contributor

lgalfaso commented Jan 4, 2015

the issue with the current patch is that is very hard to know what actually changed and what was only moved. Whatever makes this easy, works for me

@petebacondarwin
Copy link
Contributor Author

Well pretty much everything that was related to ngOptions changed :-)

@petebacondarwin petebacondarwin force-pushed the select-refact branch 2 times, most recently from d4b1762 to 6f38132 Compare January 4, 2015 20:21
@petebacondarwin
Copy link
Contributor Author

@lgalfaso - How's that?

@lgalfaso
Copy link
Contributor

lgalfaso commented Jan 5, 2015

what will happen if the same option value shows up several times? This can happen when the two options are in different option groups

@petebacondarwin
Copy link
Contributor Author

what will happen if the same option value shows up several times? This can happen when the two options are in different option groups

Good question, I have to check the previous implementation but I think it didn't support multiple options with the same value either,

@JohnYoungers
Copy link

Even if the previous implementation did support it, is that really a valid requirement? How would you even select the correct item in the list the next time they view that page? you'd have to 'track by' group and value.

@petebacondarwin
Copy link
Contributor Author

@jayoungers - thanks for commenting. I agree that this requirement would be tricky. You could certainly create a track by expression that was unique for a given group/value combination.

@lgalfaso
Copy link
Contributor

lgalfaso commented Jan 5, 2015

I was thinking when there is no value, and the text is used

@petebacondarwin
Copy link
Contributor Author

@lgalfaso - so you are referring to the standard select and option directive combination rather than ngOptions? I don't know if this was ever supported either since the addOption method always took the value of the option as its parameter, which means that you would only get one option in the SelectController's map

@lgalfaso
Copy link
Contributor

lgalfaso commented Jan 5, 2015

@petebacondarwin ok, makes sense

@Narretz
Copy link
Contributor

Narretz commented Jan 5, 2015

This looks like a great refactoring, because now we could even put ngOptions inside another module.

Three general observations:

  • will using a hashmap impact performance with many options? Generally, extraordinary numbers of options smell of bad ui, but it's possible, see e.g. Bad performance when selecting an item the first time in select with many items #8019 - 2500 options is definitely too much, but we should test if there's a visible regression for first render time with say, 200 options.
  • In the breaking changes you say that the application shouldn't rely on the value of the option. However if you use ngOptions to render, but submit the form to the server via action="", then your backend will probably rely on the correct select value. I guess the number of people who do this is very low, though.
  • now that ngOptions and select are two different directives, could this cause problems of interoperability with other directives? For example, if someone attached a directive to a select that is supposed to run after the select is completely initialized, could this break?

@petebacondarwin
Copy link
Contributor Author

Awesome bit of review @gkalpak - thank you. I will address those issues over the weekend.

petebacondarwin added a commit to petebacondarwin/angular.js that referenced this pull request Jan 10, 2015
…ns are loaded async

**Major reworking of select and ngOptions**:

* The `SelectController` is now used as an abstraction for the `select` and `ngOptions` directives
to override to get their desired behaviour
* The `select` directive is completely oblivious to the ngOptions directive now - the `ngOptions`
directive could be deleted without having to make any changes to the `select` directive.
* Select related directives (single/multiple/ngOptions) can provide specific versions of
`SelectController.writeValue` and `SelectController.readValue`, which are responsible for getting
the `$viewValue` in or out of the actual `<select>` element and its `<option>` children.

BREAKING CHANGE:

When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
This commit changes the actual string used as the surrogate key. We now store a string that is computed
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
item in the collection.

(This is in keeping with the way that the unknown option value is represented in the select directive.)

Before you might have seen:

```
<select ng-model="x" ng-option="i in items">
  <option value="1">a</option>
  <option value="2">b</option>
  <option value="3">c</option>
  <option value="4">d</option>
</select>
```

Now it will be something like:

```
<select ng-model="x" ng-option="i in items">
  <option value="string:a">a</option>
  <option value="string:b">b</option>
  <option value="string:c">c</option>
  <option value="string:d">d</option>
</select>
```

If your application code relied on this value, which it shouldn't, then you will need to modify your
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
as this provides the ability to specify the key that is stored.

Closes angular#9714
Closes angular#10639
@petebacondarwin petebacondarwin changed the title fix(ngOptions): ensure that the correct option is selected when options are loaded async refact(ngOptions): major refactoring... Jan 10, 2015
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this pull request Jan 11, 2015
…ns are loaded async

**Major reworking of select and ngOptions**:

* The `SelectController` is now used as an abstraction for the `select` and `ngOptions` directives
to override to get their desired behaviour
* The `select` directive is completely oblivious to the ngOptions directive now - the `ngOptions`
directive could be deleted without having to make any changes to the `select` directive.
* Select related directives (single/multiple/ngOptions) can provide specific versions of
`SelectController.writeValue` and `SelectController.readValue`, which are responsible for getting
the `$viewValue` in or out of the actual `<select>` element and its `<option>` children.

BREAKING CHANGE:

When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
This commit changes the actual string used as the surrogate key. We now store a string that is computed
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
item in the collection.

(This is in keeping with the way that the unknown option value is represented in the select directive.)

Before you might have seen:

```
<select ng-model="x" ng-option="i in items">
  <option value="1">a</option>
  <option value="2">b</option>
  <option value="3">c</option>
  <option value="4">d</option>
</select>
```

Now it will be something like:

```
<select ng-model="x" ng-option="i in items">
  <option value="string:a">a</option>
  <option value="string:b">b</option>
  <option value="string:c">c</option>
  <option value="string:d">d</option>
</select>
```

If your application code relied on this value, which it shouldn't, then you will need to modify your
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
as this provides the ability to specify the key that is stored.

Closes angular#8019
Closes angular#9714
Closes angular#10639
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this pull request Jan 11, 2015
…ns are loaded async

**Major reworking of select and ngOptions**:

* The `SelectController` is now used as an abstraction for the `select` and `ngOptions` directives
to override to get their desired behaviour
* The `select` directive is completely oblivious to the ngOptions directive now - the `ngOptions`
directive could be deleted without having to make any changes to the `select` directive.
* Select related directives (single/multiple/ngOptions) can provide specific versions of
`SelectController.writeValue` and `SelectController.readValue`, which are responsible for getting
the `$viewValue` in or out of the actual `<select>` element and its `<option>` children.

BREAKING CHANGE:

When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
This commit changes the actual string used as the surrogate key. We now store a string that is computed
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
item in the collection.

(This is in keeping with the way that the unknown option value is represented in the select directive.)

Before you might have seen:

```
<select ng-model="x" ng-option="i in items">
  <option value="1">a</option>
  <option value="2">b</option>
  <option value="3">c</option>
  <option value="4">d</option>
</select>
```

Now it will be something like:

```
<select ng-model="x" ng-option="i in items">
  <option value="string:a">a</option>
  <option value="string:b">b</option>
  <option value="string:c">c</option>
  <option value="string:d">d</option>
</select>
```

If your application code relied on this value, which it shouldn't, then you will need to modify your
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
as this provides the ability to specify the key that is stored.

Closes angular#8019
Closes angular#9714
Closes angular#10639
@lgalfaso
Copy link
Contributor

LGTM, @petebacondarwin can you land this?

…ns are loaded async

**Major reworking of select and ngOptions**:

* The `SelectController` is now used as an abstraction for the `select` and `ngOptions` directives
to override to get their desired behaviour
* The `select` directive is completely oblivious to the ngOptions directive now - the `ngOptions`
directive could be deleted without having to make any changes to the `select` directive.
* Select related directives (single/multiple/ngOptions) can provide specific versions of
`SelectController.writeValue` and `SelectController.readValue`, which are responsible for getting
the `$viewValue` in or out of the actual `<select>` element and its `<option>` children.

BREAKING CHANGE:

When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
This commit changes the actual string used as the surrogate key. We now store a string that is computed
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
item in the collection.

(This is in keeping with the way that the unknown option value is represented in the select directive.)

Before you might have seen:

```
<select ng-model="x" ng-option="i in items">
  <option value="1">a</option>
  <option value="2">b</option>
  <option value="3">c</option>
  <option value="4">d</option>
</select>
```

Now it will be something like:

```
<select ng-model="x" ng-option="i in items">
  <option value="string:a">a</option>
  <option value="string:b">b</option>
  <option value="string:c">c</option>
  <option value="string:d">d</option>
</select>
```

If your application code relied on this value, which it shouldn't, then you will need to modify your
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
as this provides the ability to specify the key that is stored.

BREAKING CHANGE:

When iterating over an object's properties using the `(key, value) in obj` syntax
the order of the elements used to be sorted alphabetically. This was an artificial
attempt to create a deterministic ordering since browsers don't guarantee the order.
But in practice this is not what people want and so this change iterates over properties
in the order they are returned by Object.keys(obj), which is almost always the order
in which the properties were defined.

Closes angular#8019
Closes angular#9714
Closes angular#10639
Since select is not aware of ngOptions, it makes sense to move it into its
own file for more easy maintenance.
Utilize the $watchDelegate on the watcher used to detect changes to the labels.

Closes angular#10687
Closes angular#10694
@petebacondarwin
Copy link
Contributor Author

Landing now.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.