AngularJS UI 路由器 - 在不重新加载状态的情况下更改 url

Posted

技术标签:

【中文标题】AngularJS UI 路由器 - 在不重新加载状态的情况下更改 url【英文标题】:AngularJS UI Router - change url without reloading state 【发布时间】:2014-06-28 09:37:19 【问题描述】:

目前我们的项目使用默认的$routeProvider,我正在使用这个“hack”,在不重新加载页面的情况下更改url

services.service('$locationEx', ['$location', '$route', '$rootScope', function($location, $route, $rootScope) 
    $location.skipReload = function () 
        var lastRoute = $route.current;
        var un = $rootScope.$on('$locationChangeSuccess', function () 
            $route.current = lastRoute;
            un();
        );
        return $location;
    ;
    return $location;
]);

controller

$locationEx.skipReload().path("/category/" + $scope.model.id).replace();

我正在考虑将routeProvider 替换为ui-router 用于嵌套路由,但在ui-router 中找不到。

有可能 - 对 angular-ui-router 做同样的事情吗?

我为什么需要这个? 让我用一个例子来解释: 创建新类别的路线是/category/newclicking 之后 SAVE 我显示 success-alert 并且我想将路由 /category/new 更改为 /caterogy/23 (23 - 是存储在数据库中的新项目的 ID)

【问题讨论】:

在ui-router中你不必为每个状态定义一个URL,你可以在不改变URL的情况下从一个状态导航到另一个状态 您要更新整个 URL 还是只更新搜索路径?我正在寻找更新搜索路径的解决方案并在这里找到它:***.com/questions/21425378/… @johnathan 真的吗?我很乐意这样做,只显示一个 URL,但 $urlRouterProvider.otherwise 似乎在 URL 上运行,而不是在状态上运行。嗯,也许我可以接受 2 个 URL,或者找到其他方法来表明它是一个无效的 URL。 【参考方案1】:

我认为您根本不需要 ui-router。 $location service 的可用文档在第一段中说,“...对 $location 的更改会反映在浏览器地址栏中。”稍后它继续说,“它没有做什么?当浏览器 URL 更改时,它不会导致整个页面重新加载。”

因此,考虑到这一点,为什么不简单地更改 $location.path(因为该方法既是 getter 又是 setter),如下所示:

var newPath = IdFromService;
$location.path(newPath);

documentation 指出路径应始终以正斜杠开头,但如果缺少,则会添加它。

【讨论】:

当我使用ui-router,并使用$location.path(URL_PATH)时,页面会自动重新渲染?! 是的,它会重新渲染。我尝试在 $locationChangeStart 上使用 event.preventDefault(),但这也不起作用 - 即它会阻止状态重新呈现,但它也会阻止 url 更新。【参考方案2】:

好的,解决了 :) Angular UI Router 有这个新方法,$urlRouterProvider.deferIntercept() https://github.com/angular-ui/ui-router/issues/64

基本上归结为:

angular.module('myApp', [ui.router])
  .config(['$urlRouterProvider', function ($urlRouterProvider) 
    $urlRouterProvider.deferIntercept();
  ])
  // then define the interception
  .run(['$rootScope', '$urlRouter', '$location', '$state', function ($rootScope, $urlRouter, $location, $state) 
    $rootScope.$on('$locationChangeSuccess', function(e, newUrl, oldUrl) 
      // Prevent $urlRouter's default handler from firing
      e.preventDefault();

      /** 
       * provide conditions on when to 
       * sync change in $location.path() with state reload.
       * I use $location and $state as examples, but
       * You can do any logic
       * before syncing OR stop syncing all together.
       */

      if ($state.current.name !== 'main.exampleState' || newUrl === 'http://some.url' || oldUrl !=='https://another.url') 
        // your stuff
        $urlRouter.sync();
       else 
        // don't sync
      
    );
    // Configures $urlRouter's listener *after* your custom listener
    $urlRouter.listen();
  ]);

我认为这个方法目前只包含在 ma​​ster 版本的 angular ui 路由器中,带有可选参数的那个(也很好,顺便说一句)。它需要使用

从源代码克隆和构建
grunt build

也可以通过

从源代码访问这些文档
grunt ngdocs

(它们被内置到 /site 目录中)//README.MD 中的更多信息

似乎还有另一种方法可以做到这一点,通过动态参数(我没有使用过)。 很多功劳归功于 nateabele。


作为旁注,这里是 Angular UI Router 的 $stateProvider 中的 可选参数,我结合上述使用:

angular.module('myApp').config(['$stateProvider', function ($stateProvider)     

  $stateProvider
    .state('main.doorsList', 
      url: 'doors',
      controller: DoorsListCtrl,
      resolve: DoorsListCtrl.resolve,
      templateUrl: '/modules/doors/doors-list.html'
    )
    .state('main.doorsSingle', 
      url: 'doors/:doorsSingle/:doorsDetail',
      params: 
        // as of today, it was unclear how to define a required parameter (more below)
        doorsSingle: value: null,
        doorsDetail: value: null
      ,
      controller: DoorsSingleCtrl,
      resolve: DoorsSingleCtrl.resolve,
      templateUrl: '/modules/doors/doors-single.html'
    );

]);

它的作用是允许解析一个状态,即使其中一个参数丢失。 SEO 是一个目的,可读性是另一个目的。

在上面的示例中,我希望 doorSingle 成为必需参数。目前尚不清楚如何定义这些。不过,它可以与多个可选参数一起使用,所以这不是一个真正的问题。讨论在这里https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090

【讨论】:

您的可选参数似乎不起作用。 Error: Both params and url specicified in state 'state'。它在docs 中说这也是无效的用法。有点失望。 您是从 master 构建的吗?请注意,在我添加解决方案时,可选参数仅包含在必须从源代码手动构建的版本中。在 v.0.3 发布之前,它不会包含在版本中。 只是一个简短的说明。感谢 nateabele,可选参数在几天前刚刚发布的 v0.2.11 中可用。 这很令人困惑:如何使用 $urlRouterProvider.deferIntercept();这样我就可以在不重新加载控制器的情况下更新参数?这并没有真正向我展示这一点。在 run 函数中,您可以评估 if 语句以同步或不同步,但我必须使用的只是旧 URL 和新 URL。我怎么知道这是一个我想要做的就是用只有两个 url 来更新参数的实例?逻辑是......(如果旧状态和新状态相同,那么不要重新加载控制器?)我很困惑。 对,我认为这个用例是嵌套状态,我不想重新加载子状态,因此被拦截。我想现在我宁愿使用绝对目标视图来做到这一点,并定义我知道不会改变的状态的视图。不管怎样,这个还是不错的。你会得到完整的 url,也就是说你可以从 url 中猜出状态。还有查询和路径参数等。如果您围绕状态和网址制作应用程序,那将是大量信息。在运行块中,您还可以访问服务等。这是否回答了您的问题?【参考方案3】:

您只需使用 $state.transitionTo 代替 $state.go $state.go 在内部调用 $state.transitionTo ,但会自动将选项设置为 location: true, inherit: true, relative: $state.$current, notify: true 。您可以拨打 $state.transitionTo 并设置 notify: false 。例如:

$state.go('.detail', id: newId) 

可以替换为

$state.transitionTo('.detail', id: newId, 
    location: true,
    inherit: true,
    relative: $state.$current,
    notify: false
)

编辑:正如 fracz 所建议的,它可以简单地是:

$state.go('.detail', id: newId, notify: false) 

【讨论】:

那不是“在重新加载状态时保持url”而不是“改变url而不重新加载状态”吗? 对我来说,它适用于以下内容:$state.transitionTo('.detail', id: newId, location: true, inherit: true, relative: $state.$current, notify: false ) 所以基本上将 notify 设置为 false 并将 location 设置为 true Chrome 中的 URL 永远不会更新。最近几个月这里有变化吗? @ArjendeVries 是的,它按预期工作,但我发现了一个意外行为。当您最终移动到新状态(即 url)时,在玩了许多 transitionTo 方法调用(无需重新加载)之后,它会重新启动旧控制器。 更简单:$state.go('.detail', id: newId, notify: false).【参考方案4】:

此设置为我解决了以下问题:

将url从.../更新为.../123时,训练控制器没有被调用两次 导航到另一个状态时,训练控制器不会再次被调用

状态配置

state('training', 
    abstract: true,
    url: '/training',
    templateUrl: 'partials/training.html',
    controller: 'TrainingController'
).
state('training.edit', 
    url: '/:trainingId'
).
state('training.new', 
    url: '/trainingId',
    // Optional Parameter
    params: 
        trainingId: null
    
)

调用状态(从任何其他控制器)

$scope.editTraining = function (training) 
    $state.go('training.edit',  trainingId: training.id );
;

$scope.newTraining = function () 
    $state.go('training.new',  );
;

训练控制器

var newTraining;

if (!!!$state.params.trainingId) 

    // new      

    newTraining = // create new training ...

    // Update the URL without reloading the controller
    $state.go('training.edit',
        
            trainingId : newTraining.id
        ,
        
            location: 'replace', //  update url and replace
            inherit: false,
            notify: false
        );     

 else 

    // edit

    // load existing training ...
   

【讨论】:

我试图使用类似的策略,但我的控制器从未从编辑页面获得 trainigId 的值,我可能在那里遗漏了什么?我尝试直接从 URL 并使用 ui-sref 访问编辑页面。我的代码和你的一模一样。 这对我有用,是迄今为止最清晰的解决方案【参考方案5】:

在这个问题上花了很多时间后,这就是我的工作

$state.go('stateName',params,
    // prevent the events onStart and onSuccess from firing
    notify:false,
    // prevent reload of the current state
    reload:false, 
    // replace the last record when changing the params so you don't hit the back button and get old params
    location:'replace', 
    // inherit the current params on the url
    inherit:true
);

【讨论】:

其他解决方案与路由提供者有关。这个解决方案适用于我的情况,比如我使用 $stateProvider 而不是使用 $routeProvider。 @eeejay 基本上这个问题只针对ui-router,你怎么能这么说,其他解决方案适用于$routerProvider$routeProvider$stateProvider 在架构上完全不同。 . 如果我们不希望后退按钮使用这个怎么办?我的意思是,在后按时转到上一个 state/url ,而不是使用不同参数的同一状态 我猜,通过这个解决方案,浏览器返回将无法正常工作,因为我们正在更改状态而不让 ui-router 知道它 我试过这个,看起来每次我使用 $state.go 时都会在我的组件上调用 $onInit() 。对我来说似乎不是 100% 好的。【参考方案6】:

我在很久以前就这样做了

$stateProvider
  .state(
    'home', 
      url: '/home',
      views: 
        '': 
          templateUrl: Url.resolveTemplateUrl('shared/partial/main.html'),
          controller: 'mainCtrl'
        ,
      
    )
  .state('home.login', 
    url: '/login',
    templateUrl: Url.resolveTemplateUrl('authentication/partial/login.html'),
    controller: 'authenticationCtrl'
  )
  .state('home.logout', 
    url: '/logout/:state',
    controller: 'authenticationCtrl'
  )
  .state('home.reservationChart', 
    url: '/reservations/?vw',
    views: 
      '': 
        templateUrl: Url.resolveTemplateUrl('reservationChart/partial/reservationChartContainer.html'),
        controller: 'reservationChartCtrl',
        reloadOnSearch: false
      ,
      'viewVoucher@home.reservationChart': 
        templateUrl: Url.resolveTemplateUrl('voucher/partial/viewVoucherContainer.html'),
        controller: 'viewVoucherCtrl',
        reloadOnSearch: false
      ,
      'addEditVoucher@home.reservationChart': 
        templateUrl: Url.resolveTemplateUrl('voucher/partial/voucherContainer.html'),
        controller: 'voucherCtrl',
        reloadOnSearch: false
      
    ,
    reloadOnSearch: false
  )

【讨论】:

【参考方案7】:

如果您只需要更改 url 但阻止更改状态:

更改位置(如果要在历史记录中替换,请添加 .replace):

this.$location.path([Your path]).replace();

防止重定向到您的状态:

$transitions.onBefore(, function($transition$) 
 if ($transition$.$to().name === '[state name]') 
   return false;
 
);

【讨论】:

【参考方案8】:

试试这样的

$state.go($state.$current.name, ... $state.params, 'key': newValue, notify: false)

【讨论】:

【参考方案9】:

打电话

$state.go($state.current, myParam: newValue, notify: false);

仍会重新加载控制器,这意味着您将丢失状态数据

为避免这种情况,只需将参数声明为动态:

$stateProvider.state(
    name: 'myState',
    url: '/my_state?myParam',
    params: 
        myParam: 
          dynamic: true,    // <----------
        
    ,
    ...
);

那你甚至不需要notify,只需调用

$state.go($state.current, myParam: newValue)

足够了。尼托!

来自documentation:

dynamictrue 时,更改参数值将 不会导致进入/退出状态。决议不会 重新获取,也不会重新加载视图。

这对构建很有用 当参数值发生变化时,组件会自行更新的 UI。

【讨论】:

以上是关于AngularJS UI 路由器 - 在不重新加载状态的情况下更改 url的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS的ui-router第一次点击激活路由加载页面,再次点击就没用,如何实现每次点击都激活加载路由一次

Angularjs ui router,路由嵌套 父controller执行问题

AngularJS:通过 UI 路由器加载 js 文件

AngularJS、ocLazyLoad 和 ui-router:如何在不将“状态”集中在 app.js 主类上的情况下按需加载

重新访问状态时,UI路由器视图未刷新

使用 AngularJS、ui.Router 和 yeoman 重新加载 Html5 Pushstate