Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing URL without changing state #64

Closed
iliakan opened this issue Mar 27, 2013 · 114 comments
Closed

Changing URL without changing state #64

iliakan opened this issue Mar 27, 2013 · 114 comments

Comments

@iliakan
Copy link

iliakan commented Mar 27, 2013

Here's the use case to prove my point.

I'm editing a Model. On save I want to change URL to /model/:id (id comes form server), but calling $state.transitionTo('model', {id: ...}) causes the state change and hence the model is RELOADED. As if it were not on client!

I'm trying to pass the existing model to the state, so that may keep it (by reusing in resolve): state.transitionTo('model', {model: modelObject}). But the normalization code stringifies the "model", so that's not an object any more.

Is what I'm doing conceptually wrong? Is there any really good reason to keep normalization which prevents it from working?

@ksperling
Copy link
Contributor

At the moment all state parameters are normalized to strings. This was the simplest approach to begin with, because any parameter that comes from the URL starts out as a string. It is also to ensure that in your example, navigating to '/model/123' behaves the same as transitionTo('model', { id: 123 }) and transitionTo('model', { id: '123' }).

I was thinking of extending this to allow arbitrary parameter types, but they would still be able to be converted to and from strings for URL handling.

I'm not sure I get your use case completely; it sounds like you're creating a new business entity of some type, and when you save it on the server you want to change the URL without changing the state? This is not something thats supported currently, and I'm not quite sure how it would work, you're expecting '/model/new' (or something like that) to map to the same state as '/model/123' and for $state to somehow know that even though 'new' and '123' are different that they represent the same parameter value? Presumably if you then navigated back from '123' to 'new' you'd actually want that to be treated as different though, so the user can create another new entity...

I think the easiest way to avoid the extra server roundtrip for reloading the object is to have a service that manages the remoting, and do some caching in there. When you save a new entity, you can prepopulate it into that cache.

@iliakan
Copy link
Author

iliakan commented Mar 28, 2013

Hi, I found the view change with transitionTo quite troublesome, because it rerenders ui-view, and spoils current editing process (no way to avoid rerendering).

Actually, I'd like /model/new to change to /model/123 on save without rerendering. Because the model has got the URL. But it seems there's no way to do so.

@ksperling
Copy link
Contributor

I'll leave this open to revisit later -- it's definitely not trivial to address.

One thing you could do is register '/model/new' with $urlRouter directly, and fetch a fresh object id from the server (without saving the object), and then redirect to '/model/(new-id)'.

@iliakan
Copy link
Author

iliakan commented Mar 28, 2013

Thank you very much for the project and for your answers.

Actually, what could work is reloadOnSearch: false, but for URLs. You know, most of time we want to change state when params change. But sometimes we just need to update the url and keep current controller/view.

The stateUpdate event (like routeUpdate from Angular) can be propagatedto help.

The use case: mode/new -> model/:id without reloading. Or any other case when valuable state is stored in the controller and there's totally no need to recreate it.

In this case, the controller mentioned in the state should know whether to go on with the state change and reloading OR it will handle the new URL on it's own. I guess, the neat & flexible solution would be to add parameter like: urlChangeHandler which is a function, injected with the current controller. This function is called on path change and decides whether to reload or not. It also may ask the controller if needed.

What do you think about that?
Do you feel like considering the patch?

@iliakan
Copy link
Author

iliakan commented Mar 28, 2013

P.S. I've found the other way around (change the URL and no state navigation at all). It seems, that works for me:

      var off = $scope.$on('$stateChangeStart', function(e) {
        e.preventDefault();
        off();
      });
      $location.path('model/123').replace();

@iliakan iliakan closed this as completed Mar 28, 2013
@xixixao
Copy link

xixixao commented Jun 9, 2013

Would find very useful to have a way of changing the URL without the state change (expensive render, but want to keep URL up to date for user to be able to come back).

@timkindberg
Copy link
Contributor

@iliakan can you elaborate on how that works exactly? I'd like to add it to the FAQ.

@laurelnaiad
Copy link

I was really curious, too -- it looked like an endlessly recursive listener at first glance.

However, the docs say $scope.$on "returns a deregistration function for this listener". So, since the listener calls the function that it returns, and that function deregisters the listener...

The listener deregisters itself the first time it is invoked -- hence it's a one-time prevention of routing when defined just-in-time before calling replace().

What jumps out at me at second glance is that $stateParams will probably still have the old values after this happens. So while the bookmarkability is achieved, any components relying on $stateParams might be misled unless they were manually updated by the app.

None of this is tested, but if you did:

//assuming $state is injected already
var off = $scope.$on('$stateChangeStart', function(evt,  toState, toParams, fromState, fromParams) {
 evt.preventDefault();
 $state.params = toParams;
 angular.copy($state.params, $stateParams);
 off();
});
$location.path('model/123').replace();

it might patch up the parameters.

Feels like hacking... I'm always up for that! ;) User beware though... this is way deep in stuff that isn't part of the API... so expect it to break down the road, if it even works now.

@ksperling
Copy link
Contributor

The 'dynamic parameters' feature should make this sort of thing unnecessary hopefully; you'll be able to change parameters marked as dynamic in $stateParams, and the location will update automatically, but no transition will happen.

@xixixao
Copy link

xixixao commented Jun 10, 2013

For anyone using this, there is a small gotcha - say you are in state with url "question/1", then use this hack to go to "question" and then, without the hack, navigate back to "question/1" - this won't trigger a state change since that's the state your app is actually in the whole time.

@laurelnaiad
Copy link

Yes to be thorough you should probably deal with that, too. See https://github.com/angular-ui/ui-router/blob/master/src/state.js#L233

@Hungor
Copy link

Hungor commented Jul 5, 2013

I used

var off = $scope.$on('$stateChangeStart', function(e) {
  e.preventDefault();
});
off();
$location.path('product/123').replace();

Since product is an URL preassigned with a state. The app still render.
Does anyone have run into this before and might have a solution?

@zerko
Copy link

zerko commented Oct 16, 2013

Any news on "dynamic parameters" ?

@nateabele
Copy link
Contributor

@zerko There were some partial efforts made, but it'll be a little while before I can take them over and complete them.

@gampleman
Copy link

The above hacks break with 2.7

@andreev-artem
Copy link

@nateabele, @ksperling is there any specific issue for 'dynamic parameters' ("you'll be able to change parameters marked as dynamic in $stateParams, and the location will update automatically, but no transition will happen") to have ability to track it? This issue is closed. I've tried to search any other similar issue - found only "dynamic query params". But it seems that's other issue.

@xixixao
Copy link

xixixao commented Jan 8, 2014

@ksperling This should be reopened, the hack was never ideal in the first place and this is a very useful behavior.

@timkindberg
Copy link
Contributor

Something is in the works.

@timkindberg timkindberg reopened this Jan 8, 2014
@themihai
Copy link

+10

@themihai
Copy link

@timkindberg anything that can be shared ? I would like to help as we need this !

@nateabele
Copy link
Contributor

This is the prerequisite: #454 -- that's as far as anyone's gotten so far.

@j0hnsmith
Copy link

+1 this is something that angular needs

@pandaiolo
Copy link

+1 with example :

I have a product page where you can click on a product variant, like a color, to reload the images and such things. URL needs to change, but not the rest of the page since the only operation is product.applyVariant(v_id).

Interested in any immediate workaround

@andreev-artem
Copy link

We use the following workaround:

        $stateProvider.state('search', {
            abstract: true,
            templateUrl: '/partials/search/search.html'
        });
        $stateProvider.state('search.widget', {
            url: '/search/{params:.*}'
        });

And serialize/deserialize search parameters to/from params
Cons - http://local/search url is impossible in that case, only http://local/search/

@ndamnjanovic
Copy link

+1
I have a list of products (which are on one route), and clicking one of the products opens popup. I need to change URL for that popup, so that someone can send that URL to a friend (which will open same popup).
But I have problems, making it to do it silently.

@joshhunt
Copy link

+1
@andreev-artem's workaround won't work for me as the URL is completely different from any of the others.

I have a modal that can be opened from any page, and it needs it's own URL. This is proving to be very difficult to do and is discouraging me from continuing to use AngularJS

@davidcunha
Copy link

I wanted to refresh the query parameters based on some form change. This is working for me:

$state.go('items', { value: value }, {notify:false, reload:true})

@cseils
Copy link

cseils commented Oct 8, 2015

@davidcunha This works for me too, until I need to go to another state.
In your example, once I go to another state, then the controller for 'items' is reloaded before the new state is loaded.

@btm1
Copy link

btm1 commented Oct 13, 2015

@davidcunha @cseils yes this method doesn't really work. You'll need to use $urlRouterProvider.deferIntercept();

@squadwuschel
Copy link

+1

the

   $state.go('items', { value: value }, {notify:false, reload:true})

is still not working right. When you navigate to the next view, the current view is also loaded again.

@tachyon-ops
Copy link

Is there a way to just change the url?
Idea: change the language/locale after which a 'translateChangeSuccess' (from angular translate) is issued and from there just change the url. State is the same, so no need to actually reload it or execute the controller again.

I've used a number of combinations from here but it doesn't seam to work, and I suspect it's because I actually have to change the $stateParams to their localized values.

Any idea?

@cseils
Copy link

cseils commented Jan 13, 2016

I'm using this inelegant solution of changing the url and not causing the controller to fully reload when leaving the state.

I put this at the top of the controller:

app.controller('TestController', function($state) {
   if ($state.transition) return;
   ...
});

Then just call:

$state.go('items', { value: value },
  {
    // 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'
  });

The controller still reloads when the state has changed, but it exits straight away.
Not a great solution, but so far it works for me.

@tachyon-ops
Copy link

Yes. Got it to work now. The concept is as stated throughout here.
I translate my url like this:

//on app.run()
$rootScope.$on('$translateChangeSuccess', function(){
      $rootScope.routerTranslate();
})
$rootScope.routerTranslate = function(){
   if($state.$current.name){ //if there's an actual state
      //newParams => iterate through params and translate them
      $state.go($state.$current.name, newParams, {notify: false});
   }
};

It's fine now. No reload (I was not creating a proper 'newParams' object), and only url change as intended.

@squadwuschel
Copy link

@ nmpribeiro yes that works, but when you navigate to a other url/state the current (old) state is also completly reloaded and this can create some troubles like wrong url, ....

@tachyon-ops
Copy link

@squadwuschel it dependes. I am only scratching the surface since I only know angular for a couple of months. Buy here is my method:

  1. I have a 'routes' array in the translations .json files from angular translation that I actually load before the angular is bootstraped so I can know of all my routes in advance.
  2. My states are constructed from native parameters.
  3. that function I showed will iterate all parameters and check if there is a native key for each, if such then translate it. If not, use the native key and translate that native key.

That means, if I have a new state it will basically re-write the process once a translation is triggered. Of course, if I change state I also do this 'routerTranslate' function.

So, that means when I reload to a new state, that new state will get translated as well. All the ways I've thought about it these one seams the best. This translation is done on rootScope, so no need to bother about it anymore. If there are translations, awesome, if there aren't, not a problem at all.

@averas
Copy link

averas commented Jan 16, 2016

Is there any change in behaviour in the new UI router 1.0 here? I've tried most, if not all, of the suggested solutions in this thread but I am not able to achieve what I want. Basically I want to show a modal (an image light box) with a specific URL, exactly like facebook does it when you look at images. The image light box modal should show up, the URL change, and when you close the modal the URL is changed back. All this so that you can copy-paste or deep-link the URL to the image if you want to.

I actually have achieved the described behaviour, but not without my application reloading the controllers once I get back from my light box and start navigating again.

@alexandrejunges
Copy link

I've tried almost all solutions suggested here, but couldn't make the url change work properly. When I call $state.go it works as expected, not re-initiating the controller, however when I move to a new state it re-initiate the old controller.

My route is something like this: host/parent/{id}/child, and I'm changing the state like this:

this.$state.go(this.$state.current.name, param, {
   notify: false,
   reload: false,
   location: 'replace'
});

Has anyone found a solution for this?

@Mikaeru001
Copy link

@alexandrejunges
Worked for me on 0.2.18

$state.go($state.$current.self.name, stateParams, {notify: false});

@averas
Copy link

averas commented Feb 29, 2016

@Mikaeru001 By "worked", do you mean that the transitions happen as expected, or do you mean that you manage to execute the transitions without the controller reloading when executing subsequent transitions? I think what most people is having trouble with is the latter. I've also managed to get the transitions to work precisely as I want, but my controllers keep reloading...

@Mikaeru001
Copy link

@averas I mean the method works as expected. URL changes and the state stays the same. Controller is not initialized twice. On 0.2.13 I had another problem with {notify: false}. On $state.go call URL changes, state isn't reloaded. But when I click a link to go to another state, the state from the previous $state.go({notify: false}) appears. It is not a case now since I'm on 0.2.18.

@Mikaeru001
Copy link

@averas Or is it exactly the same problem you described?

@averas
Copy link

averas commented Feb 29, 2016

This is what I experience.

Say that I am on state: x.y.z

I execute $state.go(a.b.c, stateParams, {notify: false, location: "replace"});

This works, the URL is replaced, nothing appears to reload or anything.

I then want to get back to my previous state, so I do a:

$state.go(x.y.z, stateParams, {notify: false, location: "replace"});

This also seem to work, the URL is changed, everything looks good. However, when I then do a normal transition to a sibling state, which normally only loads the last controller in the chain:

$state.go(x.y.a)

.. all controllers (including the ones for x and y) reload. This does only happen after I changed the URL like above. I think what most people are saying in this thread is that you do not need to go back and forth like I do for this to happen. When you start interacting after you have performed the URL-replacing trick the controller reloads.

@Mikaeru001
Copy link

@averas Sorry. Can't help you with that.

@alexandrejunges
Copy link

Hi @Mikaeru001,
I was using 0.2.15 and updated to 0.2.18, but the controller keeps being reloaded after I change the URL, as described by @averas.

Did you change another thing?

@averas
Copy link

averas commented Feb 29, 2016

@Mikaeru001 I honestly believe that you will strike the same problem as soon as you start interacting in your own application, you just don't know it yet. ;-)

@Mikaeru001
Copy link

@averas Probably.
@alexandrejunges No. Just what I've copy-pasted previously.

@orangesoup
Copy link

Any update on this one? I'm trying to do exactly what @averas said with 1.0, but so far no luck...

@MaximShoustin
Copy link

credits to @cseils if ($state.transition) return; works for me so far. For sure this hack is workaround so wait with you aligant solution.

**I added if ($state.transition) return; to all controllers that change URL without reloading

@guikubivan
Copy link

In case anybody is wondering, this is working for me now:
#1758 (comment). To be specific, just adding {dynamic: true} to a param allows me to use $state.go like normal and it does not reload the controller.

@Moouren
Copy link

Moouren commented Sep 11, 2018

Hi Everyone.
i have a situation that i have a community type page that load recent posts lazy load on scroll and on post click , open a modal and change url , but when close modal go back to previous state and do not reload page

@angular-ui angular-ui deleted a comment from findupto Sep 15, 2018
@angular-ui angular-ui locked and limited conversation to collaborators Sep 15, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests