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

How to update params of state without reload of controller? #1758

Closed
hyzhak opened this issue Feb 16, 2015 · 42 comments
Closed

How to update params of state without reload of controller? #1758

hyzhak opened this issue Feb 16, 2015 · 42 comments
Labels

Comments

@hyzhak
Copy link

hyzhak commented Feb 16, 2015

http://plnkr.co/edit/i32gBWblr3JaG2aNcnzg?p=preview
after you have something like:

$state.go('stateA', {param: newvalue}, notify: false);

any other command like:

$state.go('stateB');

or ever

$state.go('stateA.substate');

reload controller.

Steps to reproduce:

  1. push 'Reload state';
  2. push 'go to state.index1';
  3. Controller reloads will update to 1;
  4. push 'Realod state';
  5. and so on...

ReloadOnSearch

reloadOnSearch: false

Doesn't work because doesn't update params of state and if we go to 'state.index1' we will lost all params from of 'state'.

@hyzhak hyzhak changed the title How to update params without reload of controller? How to update params of state without reload of controller? Feb 16, 2015
@hyzhak
Copy link
Author

hyzhak commented Feb 16, 2015

reason that it is reloading is because locals of state is changed and doesn't pass this comparison https://github.com/angular-ui/ui-router/blob/master/src/viewDirective.js#L215, I've found one hack to prevent reload of controller it is return previous locale of state:

var localeName = 'some-name',
    previousLocals = $state.$current.locals[localeName];

$state.go('state', {param: value}, {notify: false});

$timeout(function() {
    var newLocals = $state.$current.locals[localeName];
    previousLocals.$stateParams = newLocals.$stateParams;
    $state.$current.locals[localeName] = previousLocals;
});

but it is just a hack and may won't work after some updates. So it will be better to fix this issue.

@ChenMorSays
Copy link

This hack does not work for me, any solutions? Nothing seems to fix this problem!

@christopherthielen
Copy link
Contributor

0.2.15 has much better implementation of reloadOnSearch... check it out.

@hyzhak
Copy link
Author

hyzhak commented Jun 9, 2015

@christopherthielen it's cool, but if you check example http://plnkr.co/edit/i32gBWblr3JaG2aNcnzg?p=preview which uses last 0.2.15 release we still have same problem. I have implement both approach 1st with notify = false, and second with reloadOnSearch = false. 1st ever works better. But still have a bug.

@ChenMorSays
Copy link

Agree. I am too using latest version, and have tried both solutions but I'm still experiencing this bug.

@ChenMorSays
Copy link

Christopher, do you have any idea for why this situation occurs?

@christopherthielen
Copy link
Contributor

In @hyzhak plunk, reloadOnSearch is being used wrong.

     $stateProvider
        .state('route1', {
          url: "/route1?updates",
          reloadOnSearch: false, // <--- it's a property of the state, not a transition option
          views: {

That said, reloadOnSearch apparently only works on states that are leaf nodes. So the (fixed) plunk then doesn't work properly after transitioning to a child state and changing the parent's params.

reloadOnSearch will work properly if:

  1. the current state is the state with reloadOnSearch: false
  2. you change the $location.search()
    2b) or you transition to the same state, with only search param values changed

reloadOnSearch is a bad port of a ngRoute concept, and is superseded by dynamic params in the 1.0 ui-router branch. We won't be allocating any developer time to enhancing reloadOnSearch. If somebody wanted to spend the time to backport dynamic params to 0.2.15, I would consider merging, as long as it is test-compatible with what we have in 1.0.

@ChenMorSays
Copy link

Thank you for acknowledging this issue!
I understand what you're saying, but say I have used "reloadOnSearch" set to "false",
how should I change the parameters of the state?
Should I use "notify: false"? Or any other option?

@hyzhak
Copy link
Author

hyzhak commented Jun 11, 2015

@christopherthielen yes, but I need something other than reloadOnSearch can provides. I'm trying to implement something similar to 2-way data binding between State-of-the-app and URL-search-params. But right now It works only in one way URL-search-params -> State-of-the-App. But other direction data binding State-of-the-App -> URL-search-params leads to the extra update in back direction URL-search-params -> State-of-the-App that refreshes state and controller, because of bug with notify parameter. Are there any ideas how it is possible to implement 2-way data binding on UI-Router? So we are still talking about notify not about reloadOnSearch.

@Nandan108
Copy link

Hi @christopherthielen! I also need the new dynamic params (State-of-the-App -> URL update without controller reload) for a project I'm starting now, but I can't seem to find this 1.0 branch you mention. Where can I get my hands on this new code, and if not now, when?
I don't mind being an early adopter...

@christopherthielen
Copy link
Contributor

@Gr33nM0nk
Use either $location.search('myparam', newVal) for URL->state update, or $state.go(".", { myparam: newval }); for stateparam->url update

Here are a few examples of using reloadOnSearch:
http://plnkr.co/edit/5OPoKUDnn98JiKFlyEYL?p=preview

@hyzhak notify: false is not "buggy", it does exactly what it says: it suppresses the firing of state events. Using notify: false is an ugly workaround because of lack of "dynamic params" in 0.2.x and I can't comment on a good way to use it (hint: I would never use it myself)

@Nandan108 1.0 work in progress is here: https://github.com/nateabele/ui-router/tree/new but is not ready for public consumption. We plan on creating a 1.0 branch in the main ui-router repo within the next few weeks, once we get all the tests passing and 1.0 routing again.

@hyzhak
Copy link
Author

hyzhak commented Jun 11, 2015

@christopherthielen thanks for answer, but I'm still thinking that notify: false is buggy because it doesn't force update of state until you will change state with notify: true after this it will update controller and state that was covered with first go with notify: false (you can see it from my example). I don't think that this kind of memory effect is good idea, because you will actually never prevent update of controller and state with notify: false but just delay it until you will go to the state with notify: true. So right now it is delayed the firing of state but not suppresses. But thanks for example with reloadOnSearch I will trying to work around with it to figure out is it possible to use it for 2way binding URL <-> State Param

@christopherthielen
Copy link
Contributor

notify: false doesn't claim to do anything except suppress the firing of the state events, which is exactly what it does. Anything beyond that is unspecified.

We're not going to spend any more effort on state events, since state events are deprecated in 1.0 (if you want them in 1.0+ you'll have to include a compatibility lib). notify: false may have one or two corner case uses that work fine for some people, but from an API perspective, all that I can say is "use at your own risk".

If you have a working hack that works for your use case, I say keep using it. If you have a simple leaf-node state with query params you want to update, use reloadOnSearch=false.

@hyzhak
Copy link
Author

hyzhak commented Jun 12, 2015

@christopherthielen my goal is not handle events - my goal is provide 2 way bindings URL <-> State Param as I have mentioned before. If reloadOnSearch=false be able to helps with that it will be great.

@miguelrincon
Copy link

I have found the same issue, my controller is run twice after using $state.go with notify: false.

Switched to using $location for now. But I would much rather use $state's API.

@jberzins
Copy link

Hello,

@christopherthielen

Any tips for workaround if parameter is part of path: /search/:term

Modified your plnkr for .go bits:
http://plnkr.co/edit/w5AmwG14KBetXhiiDgpS

Thanks

@christopherthielen
Copy link
Contributor

@jberzins that's what we're calling "dynamic params", and it's not ready in 0.2.x, sorry.

To quote myself:

reloadOnSearch is a bad port of a ngRoute concept, and is superseded by dynamic params in the 1.0 ui-router branch. We won't be allocating any developer time to enhancing reloadOnSearch. If somebody wanted to spend the time to backport dynamic params to 0.2.15, I would consider merging, as long as it is test-compatible with what we have in 1.0.

@christopherthielen
Copy link
Contributor

UI-Router 1.0 preview is available: https://github.com/angular-ui/ui-router/tree/feature-1.0

@nkoterba
Copy link

@hyzhak Thanks for bringing this "issue" up. I'm encountering a similar situation.

I modify the state (selected or not selected) of two radio buttons based on a url parameter ?selector=Labels.

When the user modifies the radio button, I need to update the URL. For example, if the user pushes the "Filters" radio button I want the URL to read ?selector=Filters.

I can do it with: this.$state.go('.', {'selector': selectorValue}, {notify: false}); and the URL is correctly updated. However, I have other buttons that change other parts of this "view" by actually changing the state and instead of just loading those views, the entire state/page/view is reloaded.

When I disable updating the URL to match the state of the radio buttons, I can change radio buttons and press the other buttons and although the state of the application changes, only the child ui-view is updated.

I tried your workaround, but it was still reloading the entire parent state.

Your statement here I think captures the issue perfectly:
So right now it is delaying the firing of state but not suppressing it

@miguelrincon
Copy link

I may have found a way (hack) to make it get the desired effect for my use case, by modifying both the $stateParams and $state.params directly in advance and then using $location.search.

So instead of:

$state.go('.', {key: value}, notify: false);

I used:

$stateParams[key] = value;
$state.params[key] = value;
$location.search(key, value);

In this way, the my controller won't refresh. I noted that the $stateChange* events don't run, but the the $locationChange* ones do run.

@christopherthielen, thanks for the great work on 1.0! Sorry for using such a hacky solution!

@syrys
Copy link

syrys commented Jul 16, 2015

FYI:
incase if people are still wondering, im getting these "dynamic params" working without reloading the controller by using the new ui-router (pre-alpha?) https://github.com/angular-ui/ui-router/tree/feature-1.0 instead of the stable/release branch. I havent tested enough to see if there are other issues yet.

I have to change the url using: $location.path('/some_path/' + dynamicVariable, false);

@eddiemonge eddiemonge added the bug label Aug 3, 2015
@eddiemonge
Copy link
Contributor

original was same as #2087 but then got changed to reloadOnSearch bug along the way

@christopherthielen
Copy link
Contributor

@eddiemonge scanning this thread and can't figure out what is the "reloadOnSearch bug"? The closest thing I can find is that @jberzins wants dynamic params in the path, not the URL. I feel like this can be closed as dupe

@eddiemonge
Copy link
Contributor

closing in favor of #2087

@andreibondarev
Copy link

I was able to solve the issue by using an abstract parent route and a child route described here: http://stackoverflow.com/questions/24581610/ui-router-change-state-without-rerender-reload-of-the-page#answer-24597985
The abstract option documented here: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$stateProvider#methods_state_parameters

abstract: "An abstract state will never be directly activated [i.e. the child leaf node controller will never be reloaded], but can provide inherited properties to its common children states."

@albertboada
Copy link

@hyzhak, I was probably trying to work-around a similar scenario than yours, and I knew the hack you proposed in this issue was EXTREMELY close to be the final solution for me, but sad enough it was not working (at least with the latest ui-router version 0.2.17).

My scenario:

I have this abstract state question (/questions/{id}), which is not the topmost state in my app, and 2 substates question.form (/) and question.execute (/execute). When I switch between questions, I do it with notify: false, so controllers and views don't get re-created, and change the controller data manually. After switching from question 1 to question 2 (successfully), if I went from the question.form to the question.execute state within the same question, with a normal ui-sref, controllers and views for the question superstate were being re-created, which was totally unwanted.

After a whole evening of debugging, I realised that faking the local into $state.$current.locals[localeName] was not the best approach, because by doing so you are only editing the reference to the local for the leaf current state, which will get destroyed when switching from substate to substate, making the hack useless. (the rest of the parents' locals are down the prototype chain).
The true source of the local we want to hack appears to be in $state.$current.path[stateDepth].locals[localeName] nowadays. Those are the ones actually being read by the comparator that decides if we need to keep a state or not.
Drawback is that you have to know how deep your pivot state is down the states hierarchy.

So, for anyone in my same case:

var localeName = 'questions', // <- name of the state that holds the `<ui-view>` in which the pivot state will be embedded (in my case, the parent state for `question` is `questions`).
    stateDepth = 1, // <-- Adapt this to your specific case. You need to know how deep your pivot state is in your states hierarchy (starting at 0)
    previousLocals = $state.$current.path[stateDepth].locals[localeName];

$state.go(state, {id: id}, {notify: false});

$timeout(function() {
    var newLocals = $state.$current.path[stateDepth].locals[localeName];
    previousLocals.$stateParams = newLocals.$stateParams;
    $state.$current.path[stateDepth].locals[localeName] = previousLocals;
});

Sticking to this until 1.0 sees the light.

By the way, @christopherthielen, could not find any info/docs about 1.0's dynamic params, which are supposed to be the panacea for all these problems. Is there anywhere we can read something about it? Thanks!

@modikejal10
Copy link

Have found this thing to work for me,
$state.go($state.current, {params},{notify:false,reload:$state.current});
Could update URL without state getting loaded again.
So the issue I was facing was I wanted to update the URL without loading the parent state, so I gave reload as the child state I am in, so that way when I change the tab, it doesnt reload the parent tab, and the parameter which I wanted to update was given with the child state in route defintiion.

@kashesandr
Copy link

Here the possible solution I found
http://stackoverflow.com/a/30246785/1061438

$state.transitionTo('search', {q: 'updated search term'}, { notify: false });

@muke5hy
Copy link

muke5hy commented Mar 30, 2016

For me
$state.transitionTo('search', {q: 'updated search term'}, { notify: false });
is changing the state, but its not appending param.

@christopherthielen
Copy link
Contributor

@albertboada @modikejal10 @kashesandr @muke5hy

using notify: false is almost never a good idea, and is now deprecated. Use reloadOnSearch if you must.

You can also try dynamic parameters in the 1.0 version (currently 1.0.0-alpha.3). In your state, configure a parameter as dynamic and implement the uiOnParamsChanged callback :

.state('foo', {
  url: '/:fooId',
  params: { fooId: { dynamic: true } },
  controller: function() {
    this.uiOnParamsChanged(changedParams, $transition$) {
      // do something with the changed params
      // you can inspect $transition$ to see the what triggered the dynamic params change.
    }
  }
});

@shmool
Copy link

shmool commented Apr 4, 2016

@christopherthielen First of all, thanks for all the work on UI Router!
I'm using dynamic param, and for my understanding, when the param is changed it should be reflected in the URL. Am I right, or is there something else that I should implement? I tried various methods (query/url param).
In alpha.3 $state.params.myParam and $stateParams.myParam are binded, which wasn't the case in alpha.1.
See my question: http://stackoverflow.com/questions/36375529/dynamic-parameters-in-ui-router1-0

@christopherthielen
Copy link
Contributor

@shmool yes, the url should be updating :( I'll take a look

@christopherthielen
Copy link
Contributor

@shmool have a look at this plunker: http://plnkr.co/edit/MkGSGfPXVC8b6qYqKKjP?p=preview

@shmool
Copy link

shmool commented Apr 5, 2016

@christopherthielen Thanks for the plunker!

I thought the feature worked differently - changing the param automatically changes the url. I saw Nate talking about maps with dynamic location coordinates, and I thought it would work this way. But I see I was wrong - I should use actual state transition (with ui-sref or $state.go) to change the url. What's important is that the controller instance is remained.

@motin
Copy link

motin commented Apr 13, 2016

@christopherthielen Thanks! I quoted your great responses over at http://stackoverflow.com/a/36600897/682317
Also forked the plunkr to include state changes using with $state.go and $state.transitionTo http://plnkr.co/edit/T2scUAq0ljnZhPqkIshB?p=preview

@JesterXL
Copy link

JesterXL commented Jul 6, 2016

For those trying to get this to work and can't, can't get the uiOnParamsChanged to fire, or who use Controllers for their components that read the $stateParams vs. setting the Controller in the $stateProvider, I took a different strategy that may help (I'm using 1.0.0-beta.1).

Step 1: Setting up your route with the dynamic parameters:

$stateProvider
        .state('searchresults', {
            url: '/search/?:text&:field&:start&:amount&:sortBy&:sortOrder',
            params: {
                text: {dynamic: true},
                field: {dynamic: true},
                start: {dynamic: true},
                amount: {dynamic: true},
                sortBy: {dynamic: true},
                sortOrder: {dynamic: true}
            },
            component: 'ccSearchResults'
        });

Step 2. When your Controller first fires, you can use the $stateParams:

.controller('searchResultsController', function()
{
...
    onSearch(
        $stateParams.text, 
        $stateParams.field,
        $stateParams.start,
        $stateParams.amount,
        $stateParams.sortBy,
        $stateParams.sortOrder);
...
});

Step 3. However, when you want to do a new search without reloading your Controller:

// vm = this = your controller
$state.go('searchresults', {
    text: vm.text,
    field: vm.field,
    start: vm.start,
    amount: vm.amount,
    sortBy: vm.sortBy,
    sortOrder: vm.sortOrder
})
.then(function(result)
{
    onSearch(
        $stateParams.text, 
        $stateParams.field,
        $stateParams.start,
        $stateParams.amount,
        $stateParams.sortBy,
        $stateParams.sortOrder);
});

The Promise is important as the $stateParams variable doesn't update until after the Promise has completed.

@ramtob
Copy link

ramtob commented Nov 27, 2016

@christopherthielen Hello. I am trying to look at your plunk http://plnkr.co/edit/MkGSGfPXVC8b6qYqKKjP?p=preview
But the application does not get loaded, and in the console I get this error message:
VM842 angular.js:3809 Uncaught TypeError: Cannot read property '1' of null

Is anything missing?
TIA

@christopherthielen
Copy link
Contributor

@ramtob I updated it to angular 1.5.8 and ui-router 1.0.0-beta.3 and it works now: http://plnkr.co/edit/MkGSGfPXVC8b6qYqKKjP?p=preview

@ramtob
Copy link

ramtob commented Nov 28, 2016

@christopherthielen For some reason I do not see your changes on this link. But after I made these changes myself, the plunk does work. Thanks!

@marcstober
Copy link

@christopherthielen the plunkr is using angular 1.4.9, maybe a change wasn't saved? as @ramtob said it works if updated to 1.5.8 in the script tag in index.html

@christopherthielen
Copy link
Contributor

@marcstober the plunk was "frozen" to an old version. I've unfroze it. Thanks!

@tomaszganski
Copy link

I spent hours with UI-Router 0.3.2 to disable reloading of parent controller when parameter change. Thanks guys for introducing dynamic parameters in 1.0.0 version, it resolve my problem perfectly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests