AngularJS UI router's "resolve" fires twice

3.8k views Asked by At

I am using angular ui router to handle some routing on my frontend. This is what my routing code looks like.

// angular config

$stateProvider.state('app', {
    templateUrl: '/static/partials/home.html',
    controller: 'NavCtrl'
});

$stateProvider.state('app.reader', {
    url : '/reader/*path?start&end&column&page',
    templateUrl: '/static/partials/reader.html',
    resolve : {
        panelContent : [
            '$state', '$stateParams', '$http',
            function ($state, $stateParams, $http) {
                alert('resolving panel Content');
                return [];  // simplest thing possible to illustrate my point
            }
        ]
    },
    controller: 'ReaderCtrl'
});

/// etc etc

$urlRouterProvider.otherwise('/reader/');

My html makes use of multiple nested views, I'll try and illustrate as best I can

index.html

<html>
    <div ui-view></div>     <!-- /static/partials/home.html gets injected here -->
</html>

/static/home.html

<html>
    <!-- some side bar stuff -->

    <!-- reader -->
    <div ui-view></div>    <!-- /static/partials/reader.html gets injected here -->
</html>

So I've got multiple levels of nesting going on

-- index.html
    -- home.html
        -- reader.html

Now, when I load the page for the first time, my alert message

alert('resolving panel Content');

fires just once.. that makes sense. However, let's say I click "next page" inside my pagination..

<!-- inside /static/partials/reader.html -->   

<uib
    pagination total-items= "totalItems"
    ng-model= "pageNumber"
    ng-change= "pageUpdate"
    max-size= "maxPageNumbersDisplayed"
></uib>

this eventually fires a function inside my "ReaderCtrl"

$scope.pageUpdate(page) {
    $state.go( '.', {page: page}); 
}   

This updates the url, from going to something like this

/#/reader/<my path>

to something like this

/#/reader/<my_path>?page=2

Now for the part that has me tearing my hair out.

I get back to the "resolve" code block in the reader section of my routing.

The alert message happens twice.

By doing a bit of debugging in the web console, I discovered that the order goes

 1) alert message in resolve
 2) travel through the entirety of ReaderCtrl
 3) lots and lots of angular calls
 4) alert message (2nd time)
 5) travel through entirety of ReaderCtrl a second time.

You might be inclined to know what is going on in NavCtrl, but I am not making any calls there. All that is in NavCtrl are functions that ReaderCtrl can inherit, in order to update the scope for /static/partials/home.html

So really, it appears as though I am stuck on step 3 here.

Does anyone have any ideas as to why my resolve block appears to be firing twice?

edit:

after a bit more debugging, I have seemed to figure out that the order goes something like this, starting right after the "updatePage" function executes.

1) first "resolving message"
    -- the url has not yet changed
2) second "resolving message"
    -- the url appears to have changed very shortly before this message

So, I guess my question now is... why does

$state.go('.', args);

NOT change the url before the first alert fires, but DOES change the url at/near the second alert?

edit 2: could not end up fixing my issue, so I sort of hacked around it for the time being... I essentially made a function that did what I assume $state.go() was doing behind the scenes, and constructed the url.

function _mk_url(args) {

    var url = "/reader";

    var pageNumber = args.pageNumber || 1;
    url += "?page=" + pageNumber;

    var columns = args.columns || [];
    columns.forEach(function(d) {
        url += "&column=" + d;
    });

    //etc..

    return url;
}

var args = {"columns" : ["a", "b", "c"], "pageNumber" : 2};
var url = _mk_url(args);
$location.url(url);
3

There are 3 answers

0
Дмитрий Синьков On

I'd got this problem. The reason was in my html template. I used ui-sref directive in both child and parent elements

<li ui-sref="{{r.name}}" ng-class="vm.isCurrent(r)" ng-repeat="r in vm.settingsRoutes"> <span ui-sref="{{r.name}}" ng-bind-html="r.title"></span> </li>

so when I clicked on span, I fired stateChange twice.

0
jlewkovich On

I was having this problem and found out it was because I was calling my resolve function manually somewhere else in the code. Search your code for panelContent() and you may find where it's getting triggered again.

0
nlavr On

I've had the same bug. And I found that I was changed $stateParams in one of the resolve functions. The solution is make a copy from this object and then do what you want with a copy.

resolve: {

      /** @ngInject */
      searchParams: function ($stateParams) {
        let params = angular.copy($stateParams); // good

        // good: 
        if (params.pending === undefined) {
          params.pending = true;
        }

        // bad: 
        if ($stateParams.redirect === 'true') {
          $stateParams.pending = false; // this line changing the URL
        }
        return params;
      },

}