10

I have an Angular app built using the ui.router package for its URL routing. I would like to change it so that if the user attempts to navigate to the page they're already on, the router reloads that state rather than doing nothing. Per http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state#go, $state.go does take a reload parameter that will do exactly that, however it defaults to false. Now, it strikes me as a poor idea to rewrite every single call to $state.go and every ui-sref in my code to set reload to true. What I'd like is some way to change the default value of that argument for $state.go or, failing that, at least the ui-sref decorator.

Following off of http://angular-tips.com/blog/2013/09/experiment-decorating-directives/, I attempted to at least extend the ui-sref directive (since there are a lot more of those than there are explicit $state.go calls)

myApp.config(function($provide) {
    // override ui-sref to have the reload option default to true.
    $provide.decorator('uiSrefDirective', function($delegate){
        var directive = $delegate[0];
        var link = directive.link;

        directive.compile = function() {
            return function(scope, element, attrs) {
                if(attrs.uiSrefOpts == null){
                    attrs.uiSrefOpts = {};
                }

                if(attrs.uiSrefOpts.reload == null){
                    attrs.uiSrefOpts.reload = true;
                }
                console.log(arguments);

                link.apply(this, arguments);
            };
        };
        return $delegate;
    });

However, this doesn't actually seem to accomplish anything, and even if it did, it wouldn't actually affect $state.go. Does anyone have any ideas how I might change that behavior short of doing so manually in the ui.router code?

rmehlinger
  • 1,067
  • 1
  • 8
  • 23

1 Answers1

19

I adjusted your approach below. We will profit from decorating, but not of the directive. As we can check here, in the ui-sref directive source code, the click event in fact does call:

$state.go(ref.state, params, options); 

So, we can decorate the $state provider, which could be very straightforward:

.config(function ($provide) {
    $provide.decorator('$state', function ($delegate) {

        // let's locally use 'state' name
        var state = $delegate;

        // let's extend this object with new function 
        // 'baseGo', which in fact, will keep the reference
        // to the original 'go' function
        state.baseGo = state.go;

        // here comes our new 'go' decoration
        var go = function (to, params, options) {
            options = options || {};

            // only in case of missing 'reload'
            // append our explicit 'true'
            if (angular.isUndefined(options.reload)) {

                options.reload = true;
            }

            // return processing to the 'baseGo' - original
            return this.baseGo(to, params, options);
        };

        // assign new 'go', right now decorating the old 'go'
        state.go = go;

        return $delegate;
    });        
})

And that's enough. Now any state change (including click on ui-sref) will trigger reload.

Note: Just must say, that I do not think that this is the way. Triggering reload all the time ... to me it seems that we in fact loose a lot, loose the advantages coming with that diamond - ui-router

Nitsan Baleli
  • 5,393
  • 3
  • 30
  • 52
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Thanks for this! That said, I'm not sure that reloading loses much. It only affects things when you attempt to transition to the same state--it should leave transitions to other states unaffected, yes? – rmehlinger Apr 21 '14 at 18:52
  • I am afraid, that this setting will do much more than expected. Let's have a List view for `\Employee\` and its child `\Employee\Detail\ID`. In the world of ui-router *(without our change above)* we can touch the list, then a detail of Employee 1 and then Employee 2. The `EmployeeListCtrl` (for a list view) will be init once. The `EmployeeDetailCtrl` would be triggered for each detail. **BUT** with our changes ... Even the `EmployeeListCtrl` will be reinstantiated always, i.e. **loading list data always**. Please, try to play with it...(some logging) and maybe rethink this need to *reload* – Radim Köhler Apr 22 '14 at 04:32
  • 1
    Ah I see. Yeah, that is definitely a performance hit. However in this case I think it's mostly desired behavior--those lists can be updated fairly frequently, and we want to make sure that every time the user navigates they're getting the freshest data. That said... there's probably a more efficient way to do this. Anyway, thanks again for all your help! – rmehlinger Apr 22 '14 at 19:23
  • @RadimKöhler - how do you communicate to future developers that `$state.go` has been meddled with? I'm just wondering why you wouldn't create a wrapper `function myStateGo() { /* obvious meddled code */ }`. I understand this concept relates to the decorator pattern itself, not your particular code. I was just wondering your solution to the problem of communicating this change of default behavior. – aaaaaa Feb 21 '17 at 22:37