Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

Refactoring the behavior of "as" combined with "track by"#2028

Open
aurelienlt wants to merge 3 commits intoangular-ui:masterfrom
aurelienlt:model-resolving
Open

Refactoring the behavior of "as" combined with "track by"#2028
aurelienlt wants to merge 3 commits intoangular-ui:masterfrom
aurelienlt:model-resolving

Conversation

@aurelienlt
Copy link
Copy Markdown

This pull solves most problem due to the combined usage of "as" and "track by" (#1762, #1756, ...), and most problems of the selected items not being updated by the choices items (#1989, ...). It solves the problem noticed here: https://github.com/angular-ui/ui-select/wiki/ui-select-choices#warning-select-as-and-track-by
This pull is compatible with the old behavior.

For instance, the following expressions are now valid and will result in a perfect matching between the model and the choices items in both single and multiple cases (and are tested):

  • repeat="person.email as person in people track by person.email"
  • repeat="item.deep.person as item in peopleDeep"
  • repeat="item.deep as item in peopleDeep track by item.deep.person.email"
  • repeat="choice.value.index as choice in list track by choice.value.index"
  • repeat="choice in list track by 2 * choice.index + 1"
  • repeat="choice.value.index as choice in list track by Math.pow(choice.value.index, 2) - Math.ceil(1+choice.value.index/2)"
  • repeat="Math.floor(2+Math.sqrt(choice)) as choice in list track by 6*Math.floor(2+Math.sqrt(choice))-2"

Cause of the issues

Currently, the matching between the model and the choices are performed as such:

  • in single mode, each choice is transformed into model (the "as" expression) and is compared to the model by ===
  • in multiple mode, each choice is transformed into model (the "as" expression) and is first compared by its "track by" value in the simple case ... track by itemName.prop, and second compared to the model by angular.equals

These two behaviors are both pretty approximate (for instance, item in list track by item.id is not even treated in single mode).
Moreover, the model values won't be updated if the refresh function is asynchronous.

Solution

First, the repeat parser has two new properties

  • $select.parserResult.trackByMapper: a function that turns an item into a "track by" value (null if "track by" is absent or is using $index)
  • $select.parserResult.modelToTrackByMapper: a function that turns a model (as obtained by $select.parserResult.modelMapper(item)) in a "track by" (null if impossible).

So modelToTrackByMapper(modelMapper(item)) === trackByMapper(item) when those two functions exist.

Second, a $select.resolved property (boolean in single mode, array of booleans is multiple mode) is created, saving which selected value has been matched to an item yet.

Third, at each formatting, the model (or each model item) is compared to the resolved selected items and the choices items with the following comparisons:

  • angular.equals(model, item)
  • angular.equals(model, modelMapper(item))
  • trackByMapper(model) === trackByMapper(item)
  • modelToTrackByMapper(model) === trackByMapper(item)

Matching between "as" and "track by"

The algorithm is capable of finding the real occurrences of the "as" expression in the "track by" expression as a variable (it excludes the strings, prefixes, suffixes, ...) and will fail when another use of the item is found.

  • repeat="choice.index as choice in list track by (truc.choice.index || $choice.index || 'choice.index' != 'choice' && 's\'choice.index\'s' != 's\'choice\'s' && choice.index.toUpperCase() + choice.index || 'wrong')" works and modelToTrackByMapper('right') === "RIGHTright" (fake occurrences of choice.index are not replaced)
  • repeat="choice.name as choice in list track by 2*choice.index+1" does not work, because choice.index is not using choice.name (modelToTrackByMapper = null)

However, some unlikely corner cases can be problematic: for instance, repeat="item.a + item.b as item in list track by item.a + item.b * 2" will work and be interpreted as (item.a + item.b) * 2. But repeat="(item.a + item.b) as item in list track by (item.a + item.b * 2)" won't work.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant