javascriptangularjsng-optionsangular-ngmodelangularjs-ng-options

AngularJS: ng-model switching int to string


I'm currently working on an app in Angular. So far, everything has been going -quite- well. I'm really, really new to angular and am amazed that it took so long for the first real roadblock.

Situation:

I have an array of objects each with an order.

category.items = [{id: 1, order: 1, type: {}, ...}, {id: 54, order: 2, type: {}, ...}, {id: 3, order: 3, type: {}, ...}]

The user needs to be able to rearrange these items. The new order must be set to the object property 'order'.

In html these objects are rendered like so:

<div class="category">
    <div class="item" ng-repeat="(itemIndex, item) in category.items track by $index">
        <div class="header">
        </div>
    </div>
</div>

In the header-div I have an inputfield, type select.

<select ng-model="item.order"  ng-change="changeItemOrder((itemIndex + 1), item.order, itemIndex)">
  <option ng-repeat="item in category.items" ng-value="($index + 1)">{{$index + 1}}</option>
</select>

The code for changeItemOrder:

$scope.changeItemOrder = function(old_order, new_order, item_index) {
    new_order = parseInt(new_order);
    if (old_order != new_order) {
        var upper = Math.max(old_order, new_order);
        var lower = Math.min(old_order, new_order);

        angular.forEach($scope.category.items, function(item, key) {
            if (item_index != key) {
                if (new_order < old_order) {
                    if (item_index >= new_order && (key + 1) >= lower && (key + 1) <= upper) {
                        item.order = (parseInt(item.order) + 1);
                    }
                } else if (new_order > old_order) {
                    if (item_index <= old_order && (key + 1) <= upper && (key + 1) >= lower) {
                        item.order = (parseInt(item.order) - 1);
                    }
                }
            } else {
                item.order = parseInt(new_order);
            }
        });

        $scope.reorderItems();
    }
};

(ReorderItems just call angular sorting with a default sorting mechanism comparing the orders and returning -1, 1 or 0.)

Here is where I discovered/spotted/pinpointed one of the breaking bugs in one of the possible solutions for this problem. Here I noticed that my INT is converted to string somehow, as on render an option is added to the dropdown with value 'string:2'.

I've tried ng-options, in all possible ways, but even those led to problems. The way I did ng-options was by doing item.order as item.order in ... and so on, that just made the order switch around until somehow all items had the same order. Trying different grouping methods or trackbys just gave different bugs, like suddenly introducing NaN en NULL in the dropdown, or completely removing the order property as a whole from the item-object.

So far the least bug-ridden solution has been using the ng-repeat on my options. That only causes a mismatch on the type of item.order.

Now, after searching far and wide, spending hours on stackoverflow (especially before writing up this question with that nifty little question-searching thingy) I come to you.

  1. How can I halt/circumvent the behavior where my item.order is switched from INT to STRING?

  2. If that isn't possible, how can I force my $index to be a string, so the model(string) matches the value(string)

  3. If that isn't possible, how can I write my ng-options so that I get the behavior I want? ( I've seriously tried a lot, from track by to different as and for statements, all resulted in different bugs)

    On initial load, all selects show a correct value selected, so all item.order are initially INT (I get them from our API), it's only after interacting that all but the object that triggered the reorder get messed up.


Solution

  • Eventually I was able to solve this by doing:

    <div class='header'>
        <select ng-model="item.order" ng-change="changeItemOrder((itemIndex + 1), item.order, itemIndex)">
            <option ng-repeat="thing in category.items" ng-selected="(item.order === ($index + 1))" value="{{$index + 1}}">{{$index + 1}}</option>
        </select>
    </div>
    

    This, and only this solution (so far) has what I want/need. I need the item.order model to keep track of the current position and to show the correct value on initial load. You can't set the model to questionIndex because that disturbs other HTML-elements, I can't set it to $index because that also did weird things.

    While the other solutions suggested here 'work', they do not result in the spec that was given to me. So I guess this is kinda specific.