angular-ui / ui-router

The de-facto solution to flexible routing with nested views in AngularJS
http://ui-router.github.io/
MIT License
13.53k stars 3k forks source link

Changing URL without changing state #64

Closed iliakan closed 10 years ago

iliakan commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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();
xixixao commented 11 years ago

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 commented 11 years ago

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

laurelnaiad commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

Any news on "dynamic parameters" ?

nateabele commented 11 years ago

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

gampleman commented 10 years ago

The above hacks break with 2.7

andreev-artem commented 10 years ago

@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 commented 10 years ago

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

timkindberg commented 10 years ago

Something is in the works.

themihai commented 10 years ago

+10

themihai commented 10 years ago

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

nateabele commented 10 years ago

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

j0hnsmith commented 10 years ago

+1 this is something that angular needs

pandaiolo commented 10 years ago

+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 commented 10 years ago

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 commented 10 years ago

+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 commented 10 years ago

+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

mickhansen commented 10 years ago

+1

Gahen commented 10 years ago

About https://github.com/angular-ui/ui-router/issues/64#issuecomment-19181158 On the current master (with optional url parameters) changing urls within the same states still reload the states.

Example: /a/b/:c/:d reloads the associated controller when moving from /a/b/c to /a/b/c/d.

nateabele commented 10 years ago

@Gahen I'm not sure what you mean here. Optional parameters and dynamic parameters are not the same thing.

Gahen commented 10 years ago

Oh! You are right, sorry for the confusion. I see now that they are still a future feature.

joshhunt commented 10 years ago

@nateabele Does this mean we can update the URL to anything without changing to reloading the state?

nateabele commented 10 years ago

@joshhunt Yes. There are now multiple ways to manipulate URLs and states independently. Dynamic parameters (linked above) are one. Here's another: https://github.com/angular-ui/ui-router/blob/master/src/urlRouter.js#L199-L250

mickhansen commented 10 years ago

Awesome, deferIntercept seems ideal - I hacked my work around to roughly the same way by ensuring i could bind to $locationChangeSuccess before ui-router (after seeing defaultPrevented in the update code) - Being able to do this the "official" way is obviously preferrable.

<3 ui-router

Maidomax commented 10 years ago

Is there an addition to the docs that explains how to use the newly added features?

nateabele commented 10 years ago

@Maidomax Yep, it's all in the API docs, which you can view by cloning the repo, then running grunt ngdocs.

Maidomax commented 10 years ago

@nateabele Thanks for the info. Looks cool. I ran into a problem, tho. The new deferIntercept() function exists in the src folder, but when I included it into my project with bower, after some errors, I realized that it does not exist in angular-ui-router.js (or the minified version) in the release folder. Did you forget to add the new version to the release folder, or it is not yet done?

nateabele commented 10 years ago

[O]r it is not yet done?

@Maidomax Correct. It will be available in 0.3.

daohodac commented 10 years ago

Can you provide a code snippet on how to use it? It is still not clear for me

yantakus commented 10 years ago

@nateabele How can I use version of ui-router with this file: https://github.com/angular-ui/ui-router/blob/master/src/urlRouter.js#L199-L250 ?

Tried this in my bower.json: "angular-ui-router": "git://github.com/angular-ui/ui-router.git" But there is no deferIntercept() anyway.

jbedard commented 10 years ago

Is there any timeframe for the 0.3 release?

jjaburke91 commented 10 years ago

I'm very keen to use this feature as well. I've tried using the {dynamic:true} method however I just get the following error in the console:

Invalid params in state 'myState'

Is this feature available in the current release? Will there be any more documentation on how to use this? I've looked in the official API and guide for this feature however wasn't able to find anything.

daohodac commented 10 years ago

but this currently work `$state.go('symbol', {symbolId:data.id}, {notify:false});``

does it match the requirements?

jjaburke91 commented 10 years ago

@daohodac Your exact solution hasn't quite worked for me in that it was still reloading the controller. It did however introduce me to $state.go and lead me to the solution I was looking for.

In order to have my URL updated with search parameters, I used the following $state.go function , where 'params' was a basic object with the URL parameter name and values.

$state.go('SearchResults.params', params, 
          {location:"replace", inherit:false} );

This was used alongside two states, one of which is required to be abstract in order for the controller not to be reloaded.

$stateProvider
        .state('SearchResults', {
            abstract: true,
            url: "/search-results",
            templateUrl: "searchResults.html",
            controller: 'searchResultsController'
        })
        .state('SearchResults.params', {
            url: "?param1¶m2¶m3"
        })
pokep17 commented 10 years ago

@jjaburke91 How I can recieve $stateParam with this function? $stateParam is empty

jjaburke91 commented 10 years ago

@pokep17 I've been using $state.params to access the parameters.

vladtar commented 10 years ago

@nateabele what is the timeline for the dynamic-params branch getting merged into master?

nateabele commented 10 years ago

@vladtar Pretty soon. Probably within the next few weeks.

vladtar commented 10 years ago

Great. Looking forward to it.

emdaut commented 10 years ago

+1

john-goldsmith commented 10 years ago

A coveted feature, indeed. +1