Search code examples
angularjstabsangularjs-scopeangular-ui-routerangular-material

md-tabs: setting variable for md-selected not working when calling function from inside ui-router


I have a ui-router template containing md-tabs from which some tabs are static, others are created dynamically using ng-repeat iterating through a given array of the data model.

Inside those dynamically created tabs there are three buttons to do the following

  • move tab left by one position
  • move tab right by one position

The move tab buttons call a function inside the same controller. The $index value of the currently shown tabs as well as the desired direction (-1 for moving left, 1 for moving right) are passed to the function.

So the HTML-snippet for the view looks as follows:

<md-content flex layout-padding>
    <md-card>
        <md-tabs md-dynamic-height md-border-bottom md-autoselect md-swipe-content md-selected="selectedTab">
            <md-tab id="{{ 'tab' + $index }}" data-ng-repeat="tab in tabs track by $index">
                <md-tab-label>Tab {{ $index + 1 }}</md-tab-label>
                <md-tab-body>
                    <md-card-title>
                        <md-card-title-text>
                            <div flex layout="row">
                                <div flex><span class="md-headline">Tab {{ $index + 1 }}</span></div>
                                <div flex align="right">
                                    <md-button ng-click="moveTab(-1, $index)">Move tab left</md-button>
                                    <md-button ng-click="moveTab(1, $index)">Move tab right</md-button>
                                </div>
                            </div>
                        </md-card-title-text>
                    </md-card-title>
                    <md-card-content>
                    <p>This is tab {{ $index + 1 }}</p>
                    </md-card-content>
                </md-tab-body>
            </md-tab>
        </md-tabs>
    </md-card>
</md-content>

The function moving the tabs is implemented in MainController as:

$scope.moveTab = function(direction, TabIdx) {
    var staticTabs = 3

    var arr = $scope.tabs
    var fromIdx = sectorIdx
    var toIdx = tabIdx + direction

    // correct `toIdx` in order to prevent index overflow for first/last array element
    if (toIdx < 0) {
        toIdx = arr.length - 1
    } else if (toIdx > arr.length-1) {
        toIdx = 0
    }
    else {
        ; // nothing to do here since `toIdx` does not need to be corrected
    }

    var tab = arr.splice(fromIdx, 1)[0]
    arr.splice(toIdx, 0, tab)

    $scope.selectedTab = staticTabs + toIdx
    console.log($scope.selectedTab)
}    

After clicking the desired button to move a tab, the data of the according tab neigbour is displayed as desired. This shows that maniupulating the $scope.tabs array works correctly. Additionally the log-message shows that even the new value for $scope.selectedTab is calculated correctly. However, the new tab is not selected in the view.

This confuses me hence both variables $scope.tabs and $scope.selectedTab are defined in the same controller and should be part of the same $scope instance. In addition, the manipulated $scope.tabs array is used in other views and shows the tab data in the new order, whereas the new value for $scope.selectedTab does not seem to be available and the shown tab does not change.


Solution

  • This was a problem of scope inheritance since data-bindings using other datatypes than objects do not have two-way-binding when inheriting the parent scope.

    See the docs:

    Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work. What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. New AngularJS developers often do not realize that ng-repeat, ng-switch, ng-view and ng-include all create new child scopes, so the problem often shows up when these directives are involved. [...]

    This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models [...]

    After changing

    selectedTab
    

    to

    selectedTab.inputTab
    

    in both the given HTML- and JS-snippets everything works as expected.