摘要:运行 ionic1 的官方 tab 示例 Demo,新添加一个子页面一切正常,但是当把新页面的 URL 改成与任一 tab 名字相似却不显示页面了。反复尝试发现是 $ionicTab 源码的原因,详细解析如下。
一、冲突复现
1. 新建项目
ionic start myApp tabs
ionic serve
2. 新增页面
① templates 文件夹下创建 new.html
<ion-view view-title="Dashboard">
<ion-content class="padding">
<h2>New Page</h2>
</ion-content>
</ion-view>
② app.js 下增加 state
.state('tab.new', {
url: '/new',
views: {
'tab-dash': {
templateUrl: 'templates/new.html',
controller: 'NewCtrl'
}
}
})
③ 修改 controller.js
.controller('DashCtrl', function($scope, $state) {
$scope.go = function() {
$state.go('tab.new');
}
})
.controller('NewCtrl', function($scope, $stateParams, Chats) {
})
④ 修改 tab-dash.html
<button class="button button-full" ng-click="go()">state:tab.new</button>
⑤ 运行项目,进入新页面,一切正常
3. 故障复现
① 修改 app.js 中新页面的路径
.state('tab.new', {
url: '/chats1', //只改了这一行
views: {
'tab-dash': {
templateUrl: 'templates/new.html',
controller: 'NewCtrl'
}
}
})
② 运行项目,进入新页面,页面空白,且 tab 标记为 Chats 标签
二、原因分析
1. 浏览器 Debug
浏览器中查看页面元素,发现多了一个空的 <ion-nav-view name="tab-chats">
,大致猜测是 URL 解析错误导致 Chats 所属的 tab 误加载了
2. 对比两次页面
对比页面发现也只多了这一部分,其他的代码没有区别
3. 修改 tabs.html 测试
将 href 从 #/tab/chats
改为 #/tab/ccc
,发现页面可以正常显示了,由此锁定为 <ion-tab> 的解析问题
,下面查看源码分析原因
<!-- Chats Tab -->
<ion-tab title="Chats" icon-off="ion-ios-chatboxes-outline" icon-on="ion-ios-chatboxes" href="#/tab/ccc">
<ion-nav-view name="tab-chats"></ion-nav-view>
</ion-tab>
三、源码解析
查看 ionic.bundle.js 的 61639 行 $ionicTab
指令的定义,发现 hrefMatchesState ()
方法进行了 URL 匹配,此函数将 当前 URL 与 tab 所属的 URL 进行 indexOf 比对查找,chats1 与 chats 成功进行了匹配,所以导致 tab 选择错误。
IonicModule
.controller('$ionicTab', [
'$scope',
'$ionicHistory',
'$attrs',
'$location',
'$state',
function($scope, $ionicHistory, $attrs, $location, $state) {
this.$scope = $scope;
//All of these exposed for testing
this.hrefMatchesState = function() {
return $attrs.href && $location.path().indexOf(
$attrs.href.replace(/^#/, '').replace(/\/$/, '')
) === 0;
};
this.srefMatchesState = function() {
return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);
};
this.navNameMatchesState = function() {
return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);
};
this.tabMatchesState = function() {
return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();
};
}]);
四、解决措施
从正则表达式看出,函数去掉了 tab 中 URL 前面的 #
和后面的 /
,这样导致误匹配的概率大大增加,因此我们保留后面的斜杠,并且在 tabs.html 中的 href 后面全部增加 /
即可解决错误匹配导致的冲突问题,代码如下所示:
① 修改 tabs.html
<ion-tabs class="tabs-icon-top tabs-color-active-positive">
<!-- Dashboard Tab -->
<ion-tab title="Status" icon-off="ion-ios-pulse" icon-on="ion-ios-pulse-strong" href="#/tab/dash/">
<ion-nav-view name="tab-dash"></ion-nav-view>
</ion-tab>
<!-- Chats Tab -->
<ion-tab title="Chats" icon-off="ion-ios-chatboxes-outline" icon-on="ion-ios-chatboxes" href="#/tab/chats/">
<ion-nav-view name="tab-chats"></ion-nav-view>
</ion-tab>
<!-- Account Tab -->
<ion-tab title="Account" icon-off="ion-ios-gear-outline" icon-on="ion-ios-gear" href="#/tab/account/">
<ion-nav-view name="tab-account"></ion-nav-view>
</ion-tab>
</ion-tabs>
② 修改 ionic.bundle.js
IonicModule
.controller('$ionicTab', [
'$scope',
'$ionicHistory',
'$attrs',
'$location',
'$state',
function($scope, $ionicHistory, $attrs, $location, $state) {
this.$scope = $scope;
//All of these exposed for testing
this.hrefMatchesState = function() {
console.log('$attrs.href : ' + $attrs.href);
console.log('$location.path : ' + $location.path());
console.log('replace : ' + $attrs.href.replace(/^#/, ''));
console.log('indexOf : ' + $location.path().indexOf(
$attrs.href.replace(/^#/, '').replace(/\/$/, '')
));
// return $attrs.href && $location.path().indexOf(
// $attrs.href.replace(/^#/, '').replace(/\/$/, '')
// ) === 0;
return $attrs.href && $location.path().indexOf(
$attrs.href.replace(/^#/, '')//此处去掉后斜杠的替换操作
) === 0;
};
this.srefMatchesState = function() {
return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);
};
this.navNameMatchesState = function() {
return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);
};
this.tabMatchesState = function() {
return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();
};
}]);