AngularJS UI路由器-更改URL而不重新加载状态


134

当前,我们的项目正在使用default $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();

我想到的替换routeProviderui-router筑巢路线,但无法找到这ui-router

有可能angular-ui-router吗?

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


1
在ui-router中,您不必为每个州定义URL,您可以在不更改URL的情况下从一个州导航到另​​一个州
Jonathan

您要更新整个URL还是仅更新搜索路径?我正在寻找一个解决方案更新的搜索路径,发现它在这里:stackoverflow.com/questions/21425378/...
弗洛里安湖

@johnathan真的吗?我很乐意这样做,只显示一个URL,但$urlRouterProvider.otherwise似乎在URL上操作,而不是状态。嗯,也许我可以使用2个URL,或者找到其他方式表明它是无效的URL。
Mawg说恢复Monica

Answers:


164

只需使用即可 $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}) 

16
那不是“在重新加载状态时保留网址”,而不是“在不重新加载状态时更改网址”吗?
Peter Hedberg 2014年

25
对我来说,它适用于以下情况: $state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false }) 所以基本上将notify设置为false,而location设置为true
Arjen de Vries 2014年

6
@ArjendeVries是的,它按预期运行,但是我发现了意外的行为。当您最终移动到新状态(即url)后,尝试了许多transitionTo方法调用(无需重新加载)后,它会重新启动旧控制器。
Premchandra Singh

2
@Premchandra Singh:这里有同样的问题。离开状态时,旧控制器将再次初始化。我从wiherek获得的解决方案遇到了同样的问题。另请参见此处github.com/angular-ui/ui-router/issues/64
martinoss 2014年

15
甚至更简单:$state.go('.detail', {id: newId}, {notify: false})
fracz

48

好了,解决了 :) 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();
  }]);

我认为目前该方法仅包含在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是一个目的,可读性是另一个目的。

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


看来您的可选参数无效。Error: Both params and url specicified in state 'state'。它在文档中说这也是无效的用法。有点令人失望。
Rhys van der Waerden 2014年

1
你是从师父那里建的吗?请注意,在添加解决方案时,可选参数仅包含在必须从源代码手动构建的版本中。直到v.0.3发布,此版本才包含在发行版中。
wiherek 2014年

1
请注意。多亏了nateabele,可选参数才在几天前发布的v0.2.11中可用。
rich97

这非常令人困惑:如何使用$ urlRouterProvider.deferIntercept(); 这样我就可以更新参数而无需重新加载控制器?这并没有真正告诉我。在run函数中,您可以评估if语句以同步还是不同步,但是我需要使用的只是新旧网址。我怎么知道这是一个我只想用两个URL更新参数的实例?逻辑会是...。(如果旧状态和新状态相同,则不要重新加载控制器?)我很困惑。
btm1

是的,我认为此用例具有嵌套状态,我不想重新加载子状态,因此正在拦截。我想现在我宁愿使用绝对目标视图,并在我知道不会改变的状态下定义视图。无论如何,这仍然很好。您将获得完整的URL,即可以从URL中猜测状态。还有查询和路径参数,等等。如果使您的应用程序围绕状态和URL,那是一堆信息。在运行块中,您还可以访问服务等。这是否回答了您的问题?
wiherek

16

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

$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 2015年

@eeejay基本问题已经被问了ui-router而已,你怎么能这么说,其他解决方案进行工作$routerProvider$routeProvider$stateProvider完全不同的架构..
潘卡Parkar

如果我们不希望后退按钮可以使用该怎么办?我的意思是,按返回上一状态/ url,而不是使用不同的参数进入同一状态
gaurav5430 '16

我猜想,通过此解决方案,浏览器将无法正常工作,因为我们正在更改状态而不通知ui路由器
Pankaj Parkar

2
我尝试了此操作,$onInit()每次使用时,组件上都会调用它$state.go。对我来说似乎不是100%可以。
Carrm

8

呼唤

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

仍会重新加载控制器。

为了避免这种情况,您必须将参数声明为dynamic:

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

然后,您甚至不需要notify,只需致电

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

足够了。Neato!

文档中

dynamic为时true,更改参数值将不会导致进入/退出状态。解析将不会重新获取,也不会重新加载视图。

[...]

这对于在构建UI时很有用,在该UI中,当参数值发生更改时组件会自行更新。


7

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

  • .../到更新网址时,不会两次调用训练控制器.../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访问编辑页面。我的代码与您的代码完全一样。
Nikhil Bhandari

这对我
有用

3

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

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

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

防止重定向到您的状态:

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

2

我这样做是在很早以前的版本:UI-router v0.2.10:

$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
  })


-6

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

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

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

文件指出,该路径应该始终以正斜杠开始,但是这会增加它,如果它缺少。


当我使用ui-router和时$location.path(URL_PATH),页面会自动重新呈现吗?
Kousha 2014年

是的,它重新呈现。我尝试在$ locationChangeStart上使用event.preventDefault(),它也不起作用-即它阻止了状态的重新呈现,但也阻止了URL的更新。
wiherek 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.