AngularJS UI Router - change url without reloading state

给你一囗甜甜゛ 提交于 2019-11-26 04:30:41

问题


Currently our project is using default $routeProvider, and I am using this \"hack\", to change url without reloading page:

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;
}]);

and in controller

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

I am thinking of replacing routeProvider with ui-router for nesting routes, but cant find this in ui-router.

Is it possible - do the same with angular-ui-router?

Why do I need this? Let me explain with an example :
Route for creating new category is /category/new after clicking on SAVE I show success-alert and I want to change route /category/new to /caterogy/23 (23 - is id of new item stored in db)


回答1:


Simply you can use $state.transitionTo instead of $state.go . $state.go calls $state.transitionTo internally but automatically sets options to { location: true, inherit: true, relative: $state.$current, notify: true } . You can call $state.transitionTo and set notify: false . For example:

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

can be replaced by

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

Edit: As suggested by fracz it can simply be:

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



回答2:


Ok, solved :) Angular UI Router has this new method, $urlRouterProvider.deferIntercept() https://github.com/angular-ui/ui-router/issues/64

basically it comes down to this:

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();
  }]);

I think this method is currently only included in the master version of angular ui router, the one with optional parameters (which are nice too, btw). It needs to be cloned and built from source with

grunt build

The docs are accessible from the source as well, through

grunt ngdocs

(they get built into the /site directory) // more info in README.MD

There seems to be another way to do this, by dynamic parameters (which I haven't used). Many credits to nateabele.


As a sidenote, here are optional parameters in Angular UI Router's $stateProvider, which I used in combination with the above:

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'
    });

}]);

what that does is it allows to resolve a state, even if one of the params is missing. SEO is one purpose, readability another.

In the example above, I wanted doorsSingle to be a required parameter. It is not clear how to define those. It works ok with multiple optional parameters though, so not really a problem. The discussion is here https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090




回答3:


After spending a lot of time with this issue, Here is what I got working

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



回答4:


This setup solved following issues for me:

  • The training controller is not called twice when updating the url from .../ to .../123
  • The training controller is not getting invoked again when navigating to another state

State configuration

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

Invoking the states (from any other controller)

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

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

Training Controller

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



回答5:


If you need only change url but prevent change state:

Change location with (add .replace if you want to replace in history):

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

Prevent redirect to your state:

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



回答6:


i did this but long ago in version: v0.2.10 of UI-router like something like this::

$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:


Calling

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

will still reload the controller.

To avoid it, you have to declare the parameter as dynamic:

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

Then you don't even need the notify, just calling

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

suffices. Neato!

From the documentation:

When dynamic is true, changes to the parameter value will not cause the state to be entered/exited. The resolves will not be re-fetched, nor will views be reloaded.

[...]

This can be useful to build UI where the component updates itself when the param values change.




回答8:


Try something like this

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



回答9:


I don't think you need ui-router at all for this. The documentation available for the $location service says in the first paragraph, "...changes to $location are reflected into the browser address bar." It continues on later to say, "What does it not do? It does not cause a full page reload when the browser URL is changed."

So, with that in mind, why not simply change the $location.path (as the method is both a getter and setter) with something like the following:

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

The documentation notes that the path should always begin with a forward slash, but this will add it if it's missing.



来源:https://stackoverflow.com/questions/23585065/angularjs-ui-router-change-url-without-reloading-state

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!