Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

Problems when using UI-Router with UI-Bootstrap Pagination Directive #2173

Closed
jbeckton opened this issue May 9, 2014 · 27 comments
Closed

Problems when using UI-Router with UI-Bootstrap Pagination Directive #2173

jbeckton opened this issue May 9, 2014 · 27 comments

Comments

@jbeckton
Copy link

jbeckton commented May 9, 2014

I am building SPA that supports using the browser back button. To accomplished this I am using the $state.go() method when the pagination links are clicked. Additionally, when the views load I am taking the url/route params and initializing my model with them.

What I am trying to do is.. when you click a page link it fires the $root.loadPage() handler which then calls $state.go("userlist", { page: $scope.model.pagination.currentPage }); so that the url gets updated and javascript history is updated. When the view reloads and my init() function is executed I then take the page param from the url to update the currentPage variable from it's default, then the $root.loadPage() method is fired again because I just set the currentPage value from the url params hense calling the $state.go() method again..

div ng-init="init()"
pagination total-items="model.pagination.totalUsers" ng-model="model.pagination.currentPage" items-per-page="model.pagination.itemsPerPage" max-size="7" ng-change="loadPage()" class="pull-right" /pagination

/div

$scope.init = function () {
$scope.model.pagination.currentPage = $stateParams.page || 1;
$scope.getUsers();
};

$scope.loadPage = function () {
$state.go("userlist", { page: $scope.model.pagination.currentPage });
};

@pkozlowski-opensource
Copy link
Member

@jbeckton I see, but I'm not sure what would you expect us to do. Are you suggesting that there is a bug / missing feature in the pagination directive? Or is it just a general support question?

@jbeckton
Copy link
Author

jbeckton commented May 9, 2014

I don't see it as a bug but more of a missing feature / feature request. Maintaining history across states/views is a fairly common use case but is not possible with the way the pagination is implemented. If there is a more appropriate place to post a feature request then point me in that direction.

@pkozlowski-opensource
Copy link
Member

@jbeckton I'm not sure I understand what you mean by:

Maintaining history across states/views is a fairly common use case but is not possible with the way the pagination is implemented

The pagination directive is not maintaining any state by itself, but rather is using the ngModel directive to bind state to whatever variable you want to use. And you can choose lifetime of this variable.

Once again, it is not clear at all at this point what is your exact problem / request so I'm going to close this issue for now. I would be happy to re-open if you've got a minimal plunk illustrating the problem discussed here.

@jbeckton
Copy link
Author

I will throw together a plunk tonight and respond with it's link.

@jbeckton
Copy link
Author

@jbeckton
Copy link
Author

It appears that the pagination directive is coded to only support client side paging, correct me if i am wrong.

imagine you have a paged list of user records with a link to a user details view in each row. You click on page 3 and click the details link for the 2nd record in the list to navigate to the details page for that user. Then when you navigate back to the list page the grid is reset back to it's initial state, page 1. This makes for a poor user experience.

If the directive would support server side paging as well as allow a developer to pass in the current page and total items in order for the directive to render in a previous state without calling the on change handler it would be a great help.

@joshdmiller
Copy link
Contributor

The directive neither supports nor prevents server-side pagination. No directive does. You can save state using ui-router to the URL and restore it in your controller. Because it uses ngModel, so long as you restore the state to your scope variable, the directive will show the right page. I'm not clear what your specific issue is either, but it's almost certainly with how to use ui-router to save/restore state than it is with the pagination directive.

@jbeckton
Copy link
Author

jbeckton commented Jun 3, 2014

Josh,

When you pass the scope variable to the paging directive it will fire the
on change event. I am using the on change event, the only means to which I
can do the navigation when a page is selected. You cannot provide the
desired state to the directive without it firing the on change event. When
the view loads you will have to pass the values into the directive so that
it can render correctly. Some of those values cannot be known until after
the data is returned from the server via promise, The promise is not
resolved until after the directive has already loaded, So when you provide
the total number of items the directive will fire the on change event.

In the case of server side paging you have to provide state to the
directive after the data set loads i.e total number of records. This then
fires the on change event which in turn tries to navigate to the page
number previously set.

My use case is nothing more than a common master detail scenario while
using ui-router to manage browser history. Have you successfully
implemented list and detail views using ui-router to maintain browser
history and this pagination directive with server side paging? If not I
challenge you to give it a shot.

On Fri, May 23, 2014 at 3:19 AM, Josh David Miller <[email protected]

wrote:

The directive neither supports nor prevents server-side pagination. No
directive does. You can save state using ui-router to the URL and restore
it in your controller. Because it uses ngModel, so long as you restore the
state to your scope variable, the directive will show the right page. I'm
not clear what your specific issue is either, but it's almost certainly
with how to use ui-router to save/restore state than it is with the
pagination directive.


Reply to this email directly or view it on GitHub
#2173 (comment)
.

@brooly
Copy link

brooly commented Jul 15, 2014

I've got the same issue, I store the state in a service but when I return to the master view the pagination directive resets to the first page, ignoring my current page number.

@jbeckton
Copy link
Author

Good luck getting any one here to comprehend your issue. I wrote my own
directive, would you like to use it?
On Jul 15, 2014 7:00 AM, "brooly" [email protected] wrote:

I've got the same issue, I store the state in a service but when I return
to the master view the pagination directive resets to the first page,
ignoring my current page number.


Reply to this email directly or view it on GitHub
#2173 (comment)
.

@brooly
Copy link

brooly commented Jul 15, 2014

Please I would like to see it.

Anyway, i've found a way to make this one works, if i load my data via ui-router resolve then the data is available before the view loading and then the pagination works.

If you want an example of how to do it I can copy&paste my code here.

@eduardoRoth
Copy link

I've got the same issue, it seems the problem is that, when you change your state, the property "data-total-items" is reset to 1. Because your data is loaded via ajax, at the moment your page loads, the pagination directive doesn't know how many items it will have, so it defaults to 1, causing to trigger the page changing event and goes back to page 1.

@jbeckton
Copy link
Author

Correct.. That's the behavior I have seen as well. This directive was
written in such a way that requires you to load the entire data set into
memory on the client and then pages through the data client side. I wanted
to do server side paging so that I could make the request size smaller but
each time I try and pass the desired state to the directive it resets it's
self back to page 1 and calls the function you define in the on change
handler.

I believe our use cases are very common but I was unable to communicate
this to the developer. It was easier to write my own paging directive than
it was to get them to comprehend what I was trying to do.

Personally I don't think they have any interest in understanding these
common use cases, "I'm not sure what would you expect us to do" and
refactor the directive to be more flexible.

i'll post a link to download my code shortly..

On Wed, Jul 23, 2014 at 7:01 PM, eduardoRoth [email protected]
wrote:

I've got the same issue, it seems the problem is that, when you change
your state, the property "data-total-items" is reset to 1. Because your
data is loaded via ajax, at the moment your page loads, the pagination
directive doesn't know how many items it will have, so it defaults to 1,
causing to trigger the page changing event and goes back to page 1.


Reply to this email directly or view it on GitHub
#2173 (comment)
.

@jbeckton
Copy link
Author

@nanounanue
Copy link

@brooly Could you paste your example code, please?

@eduardoRoth
Copy link

@jbeckton @brooly @pkozlowski-opensource I've forked the project and made some updates in the pagination.js file to allow server-side pagination.

These are the changes I've made:

  • Created factory 'ajaxItems' to mantain item count from ajax call.
  • Updated directive to allow 'showLastPageIfNotExists' parameter to be received.
  • Updated constant 'paginationConfig' to set the default value of 'showLastPageIfNotExists'
  • Updated 'totalPages' watch in controller.
  • Updated 'totalItems' watch in controller:
  • It works with ajax collections, if the ajax collection size changes between state or view changes, the function will check and update.
  • Added functionality for when the page you asked for doesn't exist, by default it'll return the user to the last page based on the size of your array (totalItems or ajaxItems.total). If you set it to false, then you'll be shown the page with no info but a functional pagination bar, so the user can go back as much as needed with a message shown in the view.

Feel free to make any changes to improve my code :)

https://github.com/eduardoRoth/bootstrap/blob/master/src/pagination/pagination.js

@brooly
Copy link

brooly commented Jul 25, 2014

@eduardoRoth great job! I will take a look.

@nanounanue Current pagination directive works perfectly just if data is loaded before the view is loaded. In order to that, you can use the "resolve" clause in the route definition. I paste one example of one of my apps to show how to do it:

.config(['$routeProvider', 'securityAuthorizationProvider', function ($routeProvider, securityAuthorizationProvider) {
  $routeProvider
    .when('/producto', {
      templateUrl: 'scripts/pedido/pedido.tpl.html',
      controller: 'PedidoCtrl',
      resolve: {
        pedidos: function(pedidoFactory, securityAuthorization, $q) {
          var deferred = $q.defer();
          securityAuthorization.requireAdminUser().then(function() {
            pedidoFactory.findAll('producto', pedidoFactory.pagOptions).then(function(data) {
              deferred.resolve(data);
            }, function(err) {
              deferred.reject(err);
            });
          }, function(err) {
            deferred.reject(err);
          });
          return deferred.promise;
        }
      }
    })

In this sample "pedidos" is injected into my controller and when the view is loaded the data is available and pagination works. Hope it helps!

@jbeckton
Copy link
Author

Data calls in your routing configuration is a really bad idea. It violates
separation of concerns and couples your routing to your data access
mechanism. It's a loophole that's gets thing running but terrible
architecture decision. Your creating some technical debt that you will have
to deal with in the future.
On Jul 25, 2014 6:14 AM, "brooly" [email protected] wrote:

@eduardoRoth https://github.com/eduardoRoth great job! I will take a
look.

@nanounanue https://github.com/nanounanue Current pagination directive
works perfectly just if data is loaded before the view is loaded. In order
to that, you can use the "resolve" clause in the route definition. I paste
one example of one of my apps to show how to do it:

.config(['$routeProvider', 'securityAuthorizationProvider', function ($routeProvider, securityAuthorizationProvider) {
$routeProvider
.when('/producto', {
templateUrl: 'scripts/pedido/pedido.tpl.html',
controller: 'PedidoCtrl',
resolve: {
pedidos: function(pedidoFactory, securityAuthorization, $q) {
var deferred = $q.defer();
securityAuthorization.requireAdminUser().then(function() {
pedidoFactory.findAll('producto', pedidoFactory.pagOptions).then(function(data) {
deferred.resolve(data);
}, function(err) {
deferred.reject(err);
});
}, function(err) {
deferred.reject(err);
});
return deferred.promise;
}
}
})

In this sample "pedidos" is injected into my controller and when the view
is loaded the data is available and pagination works. Hope it helps!


Reply to this email directly or view it on GitHub
#2173 (comment)
.

@brooly
Copy link

brooly commented Jul 31, 2014

Everything in programming is questionable, but I think that data loading in the routing is a powerful feature by angulajs and when you work with $stateProvider is really a must. When you load the data in the controller you can get many inconsistencies, and you get in problems with many directives that need this data during the view loading.

Data is preloaded (via factory promises, no logic there) and injected into the controller once resolved, I can't see any technical debt there and it's an advantage for testing.

Anyway I'm not a guru and I always like to learn from others, so please I appreciate If you could explain me any problems I would have to deal with in the feature.

Thanks!

@itaylorweb
Copy link

I experienced this problem too. (i'm using ui-bootstrap-tpls.js)

I have a querystring param of page=x which on load of controller I set the $scope.pager.currentPage to equal the page param. The page was always being reset to 1 even if the param was page=2 etc..

Below is how I fixed it. Forgive my pathetic explanation :)

In the src file there is a $watch defined for totalPages:

$scope.$watch('totalPages', function(value) {
    setNumPages($scope.$parent, value); // Readonly variable
    if ( $scope.page > value ) {
        $scope.selectPage(value);
    } else {
        ngModelCtrl.$render();
    }
});

The watch fires twice when the page loads. If i logged out 'value' (totalPages) I would get 1 then 2 (2 being my actual number of pages). As the $scope.page was greater than 'value' on the first iteration, the selectPage function is called and the page is reset to 1.

I modified the totalPages watch function as per below to fix it. I can now pass in the desired page as a querystring param and it keeps it.

$scope.$watch('totalPages', function(value, oldValue) {
    setNumPages($scope.$parent, value); // Readonly variable

    if (value !== oldValue) {
        if ( $scope.page > value ) {
            $scope.selectPage(value);
        } else {
            ngModelCtrl.$render();
        }
    } else {
            ngModelCtrl.$render();
    }
});

I don't know if this is the best approach but it worked for me.

@tkalimov
Copy link

tkalimov commented Oct 3, 2014

I think what I did might have been simpler. I changed the "ng-change" attribute on the pagination directive to be "ng-click":

<pagination ng-show="totalUsers > itemsPerPage" total-items="totalUsers" ng-model="currentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" rotate="false" num-pages="numPages" ng-click="pageChanged()" items-per-page="itemsPerPage"></pagination>

Then in the controller I have a function called pageChanged which calls $state.go. The only additional workaround I had to do is I also set javascript $scope.currentPage = $stateParams.page only after I set the total-items.

 $scope.pageChanged = function() {
        $state.go("discover", {page: $scope.currentPage}, {reload: false});
        mixpanel.track("Discover users page changed");  
      };  

@realdah
Copy link

realdah commented Oct 8, 2014

I had the same problems here with this pagination directives.

Probably, for a pagination, it should not update the model till every watching element to be a valid value.

@jacqueslareau
Copy link

@pkozlowski-opensource I've also encountered this problem. I think this issue needs to be reopened.

@arthyrik
Copy link

I have the same problem. Reopen this issue please.

@chrisirhc
Copy link
Contributor

I'm trying to reproduce this issue and the Plunker above just seems broken.
I still don't comprehend this issue from above exchange. Is it that ng-change fires when ng-model is set?

If anyone is willing to create a Plunker that demonstrates your issue, we can look into this.

@jbeckton
Copy link
Author

jbeckton commented Nov 3, 2014

I have explained the use case in depth months ago, there are no more words
that exist that I know of that can make it more clear. It's real simple,
try for yourself to implement a server side paging case where ui - router
is used to track page changes so the the browser back and forward buttons
can be used to navigate the pages of data. The ngModel and onChange are
causing the pagination to rebuilt itself starting with page 1.
On Nov 3, 2014 3:07 AM, "Chris Chua" [email protected] wrote:

I'm trying to reproduce this issue and the Plunker above just seems broken.
I still don't comprehend this issue from above exchange. Is it that
ng-change fires when ng-model is set?

If anyone is willing to create a Plunker that demonstrates your issue, we
can look into this.


Reply to this email directly or view it on GitHub
#2173 (comment)
.

@chrisirhc
Copy link
Contributor

I'm sorry to hear that you think that there's no way to make yourself clearer. I'm guessing it is also difficult to get this working with an online demonstration/example since it may require some sort of a backend.

Since no collaborator has complete understanding of this issue, I'm going to lock this issue since any response to it is invalid and should be treated as such. Any "me toos" or "+1"s make no sense because the issue at hand simply isn't clear.

Anybody who is willing to explain this issue clearly with a clear example is welcome to create another issue and refer to this.

@angular-ui angular-ui locked and limited conversation to collaborators Nov 5, 2014
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