ui.router 默认参数值导致页面刷新错误

ui.router default param value causes page refresh error

我正在试验 ui.router 让一些导航选项卡保持状态。当我意识到指定默认参数值会在我刷新页面时提供一些 unexpected/buggy/weird 行为时,我几乎要拥有我想要的一切。

我有两个参数,stateNum(1 或 2)和 subState(A、B 或 C)。 URL 的格式为 /stateTabs/state/:stateNum/sub/:subState。有问题的状态定义是

$stateProvider.state("stateTabs", {
     url:        "/stateTabs"
    ,controller: "StateTabsCtrl"

//  ,params: { // default params
//       stateNum: "2"
//      ,subState: "B"
//  }

    ,templateUrl: "tplStateTabs"
});
$stateProvider.state("stateTabs.state", {
    url: "/state/:stateNum"
});
$stateProvider.state("stateTabs.state.sub", {
    url: "/sub/:subState"
});

如果 params 属性 被启用,刷新页面将导致 stateNum 等于 URL 的 <stateNum>/sub/<subState>,并且 subState 等于默认值 "x"。好像是在做贪心匹配。单击任何选项卡将 return 恢复正常功能。

我希望在刷新时,ui.router 会看到一个 URL,比如 "stateTabs/state/2/sub/B",并将其解析为正确的 $stateParams。这是一个错误还是我做错了什么?

我最终想要完成的是能够 ui-srefstateTabs 并将默认设置为预定义的 stateNum+subState。

完整的代码片段如下,但需要将其复制到本地文件才能刷新并触发错误。

var StateTabs = (function ()
{
  "use strict";

  var module = angular.module("StateTabs", ["ng", "ui.router"]);

  module.config(["$stateProvider", "$urlRouterProvider",
    function ($stateProvider, $urlRouterProvider)
    {
      $urlRouterProvider.otherwise("/");

      // controller not instantiated if template is not defined
      $stateProvider.state("index", {
         url: "/"
      });
      $stateProvider.state("stateTabs", {
         url:        "/stateTabs"
        ,controller: "StateTabsCtrl"

/*
 * If the following property is enabled,
 * refreshing the page will cause stateNum
 * to equal the "/<stateNum>/sub/<subState>"
 *  from the URL and subState to equal the
 * default of "x". Click any tab will return
 * to normal functionality.
 */
//        ,params: { // default params
//           stateNum: "3"
//          ,subState: "x"
//        }

        ,templateUrl: "tplStateTabs"
      });

      $stateProvider.state("stateTabs.state", {
        url: "/state/:stateNum"
      });
      $stateProvider.state("stateTabs.state.sub", {
        url: "/sub/:subState"
      });
    }
  ]);

  module.controller("StateTabsCtrl", ["$scope",
    function ($scope)
    {
      $scope.$on("$stateChangeSuccess", function (event, toState, toParams, fromState, fromParams) {
        $scope.stateParams = toParams;
      });
    }
  ]);

  return module;
})();
div#stateTabs ul.tabbed-nav,
div#stateTabs ul.tabbed-sub-nav {
    display:     flex;
    flex-wrap:   nowrap;
    align-items: stretch;
    padding:     0;
    margin:      0;
    font-size:   16px;
    text-align:  center;
    line-height: 1.1;
}

div#stateTabs ul.tabbed-nav li,
div#stateTabs ul.tabbed-sub-nav li {
    padding:         1rem 2rem;
    margin-right:    1px;
    display:         flex;
    flex-flow:       column;
    justify-content: center;
}

div#stateTabs ul.tabbed-nav li:last-child,
div#stateTabs ul.tabbed-sub-nav li:last-child {
    margin-right: 0;
}

div#stateTabs ul.tabbed-nav li a,
div#stateTabs ul.tabbed-sub-nav li a {
    color:           inherit;
    text-decoration: none;
}

div#stateTabs ul.tabbed-nav {
    color:   white;
    padding: 0;
    margin:  0;
}

div#stateTabs ul.tabbed-nav li {
    background-color: black;
}

div#stateTabs ul.tabbed-sub-nav {
    justify-content: flex-start;
    color:           inherit;
    position:        relative;
}

div#stateTabs ul.tabbed-sub-nav li {
    background-color: silver;
    z-index:          10;
}

div#stateTabs ul.tabbed-nav li.selected {
    color:            black;
    background-color: #4dff4d !important;
}

div#stateTabs ul.tabbed-sub-nav li.selected {
    background-color: #269abc !important;
}
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>AngularJS Sandbox</title>
</head>

<body ng-app="StateTabs">
<h1>AngularJS Sandbox</h1>

<div ui-view><a href ui-sref="stateTabs">begin</a></div>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.13/angular-ui-router.js"></script>
<script type="text/ng-template" id="tplStateTabs">
  <div id="stateTabs">
    <ul class="tabbed-nav">
      <li ui-sref-active="selected"><a ui-sref="stateTabs.state.sub({ stateNum: '1', subState: stateParams.subState })">state 1</a></li>
      <li ui-sref-active="selected"><a ui-sref="stateTabs.state.sub({ stateNum: '2', subState: stateParams.subState })">state 2</a></li>
    </ul>
    <ul class="tabbed-sub-nav">
      <li ui-sref-active="selected"><a ui-sref="stateTabs.state.sub({ stateNum: stateParams.stateNum, subState: 'A' })">substate A</a></li>
      <li ui-sref-active="selected"><a ui-sref="stateTabs.state.sub({ stateNum: stateParams.stateNum, subState: 'B' })">substate B</a></li>
      <li ui-sref-active="selected"><a ui-sref="stateTabs.state.sub({ stateNum: stateParams.stateNum, subState: 'C' })">substate C</a></li>
    </ul>

    <div>
      <p>$stateParams: <span style="font-family: monospace">{{ stateParams }}</span></p>
      <p>stateNum: <span style="font-family: monospace">{{ stateParams.stateNum }}</span></p>
      <p>subState: <span style="font-family: monospace">{{ stateParams.subState }}</span></p>
    </div>
  </div>
</script>

</body>
</html>

这里的解决方案并不那么复杂 - 只需让 params 属于状态,在它们被定义的地方。有个workgin plunker

  $stateProvider.state("stateTabs", {
     url:        "/stateTabs"
    ,controller: "StateTabsCtrl"
    ,templateUrl: "tplStateTabs.html"

   // PARAMS do not belong here, because there is nothing related in URL

  });

  $stateProvider.state("stateTabs.state", {
    url: "/state/:stateNum",
    params: { // default params
       stateNum: "3"               // here belongs stateNum
    }
  });
  $stateProvider.state("stateTabs.state.sub", {
    url: "/sub/:subState",
    params: { // default params
      subState: "x"                // here can be subState
    }
  });

实际检查 here