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
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.
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.