141

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)

imsheth
  • 31
  • 2
  • 18
  • 36
vuliad
  • 2,142
  • 3
  • 15
  • 16
  • 1
    in ui-router you dont have to define a URL for each states, you can navigate from state to state without changing URL – Jonathan de M. May 10 '14 at 19:10
  • do you want to update the whole URL or just the search path? I was searching for a solution updating the search path and found it over here: http://stackoverflow.com/questions/21425378/supress-reloading-of-ui-router-based-view-on-query-parameter-change – Florian Loch Jul 26 '14 at 12:06
  • @johnathan Really? I would love to do that, showing only a single URL, but `$urlRouterProvider.otherwise` seems to operate on a URL, not a state. Hmmm, maybe I could live with 2 URLs, or find some other way to indicat that it is an invalid URL. – Mawg says reinstate Monica Nov 21 '16 at 10:03

10 Answers10

171

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}) 
jaym
  • 1,253
  • 13
  • 18
RezKesh
  • 2,861
  • 2
  • 24
  • 35
  • 16
    Isn't that "keep url when reloading state" instead of "change url without reloading state"? – Peter Hedberg Sep 19 '14 at 09:06
  • 25
    for me it worked with the following: `$state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false })` so basically set notify to false and location to true – Arjen de Vries Oct 06 '14 at 15:13
  • 2
    The url never gets updated in Chrome. Has it been changes here the last couple of months? – Remi Sture Nov 02 '14 at 17:41
  • 6
    @ArjendeVries Yes it is working as expected but I found an unexpected behavior. After playing around with many transitionTo method call (without reloading) when you finally moving to a new state (ie. url), it re-initiate old controller. – Premchandra Singh Dec 26 '14 at 11:56
  • 2
    @Premchandra Singh: Having the same issue here. When leaving the state, the old controller gets initialized again. I get the same problem with the accepted solution from wiherek. See also here https://github.com/angular-ui/ui-router/issues/64 – martinoss Dec 28 '14 at 13:25
  • 15
    Even simpler: `$state.go('.detail', {id: newId}, {notify: false})`. – fracz Jun 23 '15 at 07:39
  • @Premchandra I see this exact same issue: the old controller gets called again no matter how you call .go to transitionTo when you finally do move on to a new state. Has anyone found a solution for this? – btm1 Jul 29 '15 at 20:31
  • This is the perfect answer – Kimchi Man Aug 10 '15 at 19:36
  • @PremchandraSingh have you guys found a solution to the re initalising of old controller when transitionTo is used ? – NavyCody Aug 14 '15 at 11:00
  • I had to set `$state.go(".detail", {id: newId}, { location: "replace" });` – Mike Mar 29 '16 at 21:09
  • @NavyCody I think our problem with `$state.go` was its re-instantiating the controller – RezKesh Mar 31 '16 at 10:23
  • 2
    I know this is old, but in case anyone remembers... I see the same issue as @PremchandraSingh and others. Did any of you find a solution to this? I have tried setting {notify: false, reload: false, location: "replace", inherit: false} but it still re-initialises the controller when moving to a new state. – giles123 Sep 28 '16 at 16:42
  • 2
    Just FYI, I have upgraded ui.router to v1.0.0 (from the "stable" version, which I think is v0.2.x) and I no longer see the re-initialising of the old controller. It looks like it's fixed. – giles123 Feb 09 '17 at 11:40
48

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

IanB
  • 2,642
  • 22
  • 25
wiherek
  • 1,923
  • 19
  • 25
  • It seems that your optional parameters thing doesn't work. `Error: Both params and url specicified in state 'state'`. It says in the [docs](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$stateProvider) that this is invalid usage too. A bit disappointing. – Rhys van der Waerden Aug 28 '14 at 07:41
  • 1
    Did you build from master? Please note that at the point when I added the solution, the optional parameters was only included in the version that had to be built manually from source. this will not be included in the release until v.0.3 is out. – wiherek Aug 28 '14 at 10:40
  • 1
    Just a quick note. Thanks to nateabele the optional parameters are available in v0.2.11 which was just released a few days ago. – rich97 Sep 03 '14 at 08:16
  • this is very confusing: How do I use $urlRouterProvider.deferIntercept(); so that I can update the params without reloading the controller? This doesn't really show me that. Within the run function you can evaluate an if statement to either sync or not sync but all i have to work with is the old and new url. How do i know that this is an instance where all i want to do is update the params with only two urls to work with? Would the logic be.... ( if the old state and new state are the same then don't reload the controller? ) I'm confused. – btm1 Mar 10 '15 at 18:07
  • right, I think this use case was with nested states, I didn't want to reload the child state, thus was intercepting. I think now I would rather do that with absolute targeting views, and defining the view on the state that I know would not change. Anyway, this is still good. You get full urls, that is you can guess the state from the url. And also the query and path parameters, etc. If you make your app around states and urls, that is a lof of information. In the run block you can also access services etc. Does that answer your question? – wiherek Mar 11 '15 at 00:14
  • When using this method, what do you use on the "url changing side" ? do you use $location or $state.go ? I tried using $state.go but it seems that my controller is reloaded without the $locationChangeSuccess being called. – Ghislain Leveque Mar 11 '15 at 14:43
  • This operates on the url, so $location. It is in the example – wiherek Mar 11 '15 at 14:47
  • Thank for your anwser ! I've tried using $location.path('/mypath/') but nothing happens at all and the debug statements I've put in the $locationChangeSuccess handler aren't called. – Ghislain Leveque Mar 11 '15 at 14:56
  • Check if your version of ui router supports deferintercept on urlrouterprovider – wiherek Mar 11 '15 at 15:26
16

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
});
Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
  • The other solutions are related to route provider. This solution works for the case, like mine, where I am using $stateProvider and not using $routeProvider. – eeejay Nov 20 '15 at 16:12
  • @eeejay basically the question has been asked for `ui-router` only, how can you say that, other solutions were working for `$routerProvider`, `$routeProvider` & `$stateProvider` are totally different in architecture.. – Pankaj Parkar Jul 20 '16 at 19:57
  • what if we don't want the back button to work with this? I mean, on back press go to previous state/url , not to the same state with different params – gaurav5430 Jul 25 '16 at 05:15
  • I guess, by this solution browser back would not work, as we are changing a state without letting know about it to ui-router – Pankaj Parkar Jul 25 '16 at 05:40
  • Iv'e tried this but the back history still includes the old id. What could be the reason? – Rotem B Nov 06 '16 at 13:35
  • 2
    I tried this, and it looks like `$onInit()` is called on my component each time I use the `$state.go` . Doesn't seem 100% ok to me. – Carrm Nov 30 '18 at 10:40
  • AngularJs and ui-router version? – Pankaj Parkar Nov 30 '18 at 12:15
14

Calling

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

will still reload the controller, meaning you will lose state data.

To avoid it, simply 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.

Moritz Ringler
  • 9,772
  • 9
  • 21
  • 34
7

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 ...
}   
martinoss
  • 5,268
  • 2
  • 45
  • 53
  • I was trying to use a similar strategy but my controller never gets the value of trainigId from the edit page, anything that I might be missing there? I tried accessing the edit page directly from the URL and by using ui-sref. My code is exactly like yours. – Nikhil Bhandari May 01 '15 at 19:15
  • This worked for me, and is by far the most clear solution – OoDeLally Oct 10 '15 at 13:56
4

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;
 }
});
egor.xyz
  • 2,847
  • 1
  • 21
  • 18
2

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
  })
Sheelpriy
  • 1,675
  • 17
  • 28
0

Try something like this

$state.go($state.$current.name, {... $state.params, 'key': newValue}, {notify: false})
Eyad Farra
  • 4,403
  • 1
  • 24
  • 26
0

In Angular 2, the accepted answer from RezKesh translates to the following:

this.uiRouter.stateService.go(
    "home.myRouteState", 
    {
        "param1": this.myParam1,
        "param2": this.myParam2
    },
    { notify: false }
);

Assuming you have injected UIRouter into your component's constructor as follows:

constructor(
    private uiRouter: UIRouter
) { }
anemonetea
  • 51
  • 5
  • This question is specifically tagged as an AngularJS (1.x) question. Please avoid posting answers that deal with Angular (2.x+) on AngularJS questions, as this can cause confusion for readers that have the same question later on. – Hoppeduppeanut Aug 31 '22 at 07:02
  • @Hoppeduppeanut there is no equivalent question for Angular 2+ – anemonetea Sep 02 '22 at 20:19
-6

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.

Whit Waldo
  • 4,806
  • 4
  • 48
  • 70
  • When I use `ui-router`, and use `$location.path(URL_PATH)`, the page automatically re-renders?! – Kousha Jul 08 '14 at 02:11
  • yeah, it re-renders. I tried with event.preventDefault() on $locationChangeStart, that doesn't work either - i.e. it stops the state from re-rendering, but it also prevents the url from updating. – wiherek Jul 17 '14 at 16:56