I apologize if this is already tracked.
Very quickly, the default ngb-pagination component jhipster generates is builded with the following configuration:
~html
[rotate]="true">
~
Now what happen is that the pageChange event sends to loadPage the current page before the change. This means that if I am on page 1 and I try to navigate to page 2 I will still see page 1. Then if I try to navigate to page 4 I will see page 2, etc...
This way it is just unusable.
Create a new entity with pagination enabled.
I was able to fix this putting $event as input instead of page:
~html
~
[email protected] C:\Users\user\jhiptest
+-- [email protected]
`-- [email protected]
`-- [email protected] deduped
##### **JHipster configuration, a `.yo-rc.json` file generated in the root folder**
{
"generator-jhipster": {
"applicationType": "monolith",
"gitCompany": "",
"baseName": "jhiptest",
"packageName": "com.jhiptest.admin",
"packageFolder": "com/jhiptest/admin",
"serverPort": 8080,
"serviceDiscoveryType": "eureka",
"authenticationType": "jwt",
"uaaBaseName": "../uaa",
"cacheProvider": "ehcache",
"enableHibernateCache": true,
"websocket": false,
"databaseType": "sql",
"devDatabaseType": "h2Disk",
"prodDatabaseType": "mysql",
"searchEngine": "elasticsearch",
"enableSwaggerCodegen": false,
"messageBroker": false,
"buildTool": "gradle",
"useSass": true,
"clientPackageManager": "npm",
"testFrameworks": [
"protractor"
],
"enableTranslation": true,
"nativeLanguage": "it",
"languages": [
"en",
"it"
],
"clientFramework": "angularX",
"jhiPrefix": "jhi",
"jhipsterVersion": "6.5.1",
"jwtSecretKey": "-",
"embeddableLaunchScript": false,
"clientTheme": "none",
"entitySuffix": "",
"dtoSuffix": "DTO",
"otherModules": [],
"blueprints": []
},
"git-provider": "-",
"git-company": "-",
"repository-name": "-"
}
entityName.json files generated in the .jhipster directory
JDL entity definitions
entity Tag {
description String required minlength(1) maxlength(255)
}
dto Tag with mapstruct
paginate Tag with pagination
service Tag with serviceImpl
search Tag with elasticsearch
openjdk version "11.0.4" 2019-07-16
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.4+11)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.4+11, mixed mode)
git version 2.23.0.windows.1
node: v10.16.3
npm: 6.9.0
yeoman: 3.1.0
Docker version 19.03.2, build 6a30dfc
docker-compose version 1.24.1, build 4667896b
Tested on Windows 10 Home and Pro with Chrome and Firefox (latest versions)
@ChainlessWEB : Hi, thanks for the report. :smile:
I tried to recreate this issue generating the project with the exact same entities but I couldn't see any problem with the pagination. Here's a screen-cast. Are you seeing something different?

Mmm... I created a new entity and it has no problems. Then I rolled back the tag component but the bug is still there if I do not set the $event as input. Now I'm going to delete and recreate the entity entirely and let you know
Well I didn鈥檛 find any explanation for the problem but if I recreate the entity the bug is gone. I鈥檓 going to close this issue but if I鈥檒l go through this again, I鈥檒l reopen the issue so we can investigate deeper. Thank you for your time 馃檹馃徏
@ChainlessWEB : Yes, feel free to let us know if you find this consistently reoccurring. We'd be happy to investigate. :smile:
@ChainlessWEB : don't hesitate to share a project on GitHub, it's easier to reproduce the issue :)
I looked at that and I leave this comment with my findings.
I see the following in the code
<ngb-pagination [collectionSize]="totalItems" [(page)]="page" [pageSize]="itemsPerPage" [maxSize]="5" [rotate]="true" [boundaryLinks]="true" (pageChange)="loadPage(page)"></ngb-pagination>:
[(page)]="page" - if ngb-pagination variable page is changing then entity component variable page is set to that value(pageChange)="loadPage(page) - loadPage is getting parameter page which is set by [(page)]="page"Maybe sometimes is (pageChange)="loadPage(page) executed before [(page)]="page" - in this case loadPage parameter is old value and data shown and ngb-pagination are not in sync.
I think that it's correct to use $event as parameter.
I found 2 more problems if using pagination:
page parameter is set always to 1 even if this was something else than 1 - this problem is reported by ng-bootstrap/ng-bootstrap#951 and workaround is described in this comment: https://github.com/ng-bootstrap/ng-bootstrap/issues/951#issuecomment-328912382@kaidohallik : Thanks for the analysis. I wonder what triggers it though; since when the user regenerated the entity the bug seems to have vanished. And I couldn't recreate it at all at my end. :thinking:
@SudharakaP, I wasn't able to reproduce neither but current code seems suspicious. I'm reopening this issue and try to fix problems I found.
By the way, combining search and pagination brings some inconsistencies with it. The search method navigate with params but the pagination is working with query params. I customised a bit my code using query params on search and clear method:
PS. With this code the data are not loaded twice
tag.component.ts
import { Component, OnDestroy, OnInit} from '@angular/core';
import { HttpHeaders, HttpResponse } from '@angular/common/http';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { JhiEventManager, JhiParseLinks } from 'ng-jhipster';
import { NgbModal, NgbPagination } from '@ng-bootstrap/ng-bootstrap';
import { ITag } from 'app/shared/model/tag.model';
import { TagService } from './tag.service';
import { TagDeleteDialogComponent } from './tag-delete-dialog.component';
import { ITEMS_PER_PAGE } from 'app/shared/constants/pagination.constants';
@Component({
selector: 'jhi-tag',
templateUrl: './tag.component.html'
})
export class TagComponent implements OnInit, OnDestroy {
tags: ITag[];
error: any;
success: any;
eventSubscriber: Subscription;
currentSearch: string;
routeData: any;
links: any;
totalItems: any;
itemsPerPage: any;
page: any;
predicate: any;
previousPage: any;
reverse: any;
constructor(
protected tagService: TagService,
protected parseLinks: JhiParseLinks,
protected activatedRoute: ActivatedRoute,
protected router: Router,
protected eventManager: JhiEventManager,
protected modalService: NgbModal
) {
this.itemsPerPage = ITEMS_PER_PAGE;
this.routeData = this.activatedRoute.data.subscribe(data => {
this.page = data.pagingParams.page;
this.previousPage = data.pagingParams.page;
this.reverse = data.pagingParams.ascending;
this.predicate = data.pagingParams.predicate;
});
this.currentSearch =
this.activatedRoute.snapshot && this.activatedRoute.snapshot.queryParams['search']
? this.activatedRoute.snapshot.queryParams['search']
: '';
}
loadAll() {
if (this.currentSearch) {
this.tagService
.search({
page: this.page - 1,
query: this.currentSearch,
size: this.itemsPerPage,
sort: this.sort()
})
.subscribe((res: HttpResponse<ITag[]>) => this.paginateTags(res.body, res.headers));
return;
}
this.tagService
.query({
page: this.page - 1,
size: this.itemsPerPage,
sort: this.sort()
})
.subscribe((res: HttpResponse<ITag[]>) => this.paginateTags(res.body, res.headers));
}
loadPage(page: number) {
if (page !== this.previousPage) {
this.previousPage = page;
this.page = page;
this.transition();
}
}
transition() {
this.router.navigate(['/tag'], {
queryParams: {
page: this.page,
size: this.itemsPerPage,
search: this.currentSearch,
sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc')
}
});
this.loadAll();
}
clear() {
this.page = 0;
this.currentSearch = '';
this.router.navigate(['/tag'], {
queryParams: {
page: this.page,
sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc')
}
});
this.loadAll();
}
search(query: string) {
if (!query) {
return this.clear();
}
this.page = 0;
this.currentSearch = query;
this.router.navigate(['/tag'], {
queryParams: {
search: this.currentSearch,
page: this.page,
sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc')
}
});
this.loadAll();
}
ngOnInit() {
this.loadAll();
this.registerChangeInTags();
}
ngOnDestroy() {
this.eventManager.destroy(this.eventSubscriber);
}
trackId(index: number, item: ITag) {
return item.id;
}
registerChangeInTags() {
this.eventSubscriber = this.eventManager.subscribe('tagListModification', () => this.loadAll());
}
delete(tag: ITag) {
const modalRef = this.modalService.open(TagDeleteDialogComponent, { size: 'lg', backdrop: 'static' });
modalRef.componentInstance.tag = tag;
}
sort() {
return [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')];
}
loadAllIfEmpty(input: KeyboardEvent) {
if (input.key !== 'Enter' && this.currentSearch === '') {
this.clear();
}
}
protected paginateTags(data: ITag[], headers: HttpHeaders) {
this.links = this.parseLinks.parse(headers.get('link'));
this.totalItems = parseInt(headers.get('X-Total-Count'), 10);
this.tags = data;
}
}
tag.component.html:
<div>
<h2 id="page-heading">
<span jhiTranslate="aniplayApp.tag.home.title">Tags</span>
<button [routerLink]="['/tag/new']" class="btn btn-primary float-right jh-create-entity create-tag"
id="jh-create-entity">
<fa-icon [icon]="'plus'"></fa-icon>
<span class="hidden-sm-down" jhiTranslate="aniplayApp.tag.home.createLabel">
Create a new Tag
</span>
</button>
</h2>
<jhi-alert-error></jhi-alert-error>
<jhi-alert></jhi-alert>
<div class="row">
<div class="col-sm-12">
<form class="form-inline" name="searchForm">
<div class="input-group w-100 mt-3">
<label class="d-block w-100 small mb-1" for="currentSearch"
jhiTranslate="aniplayApp.tag.home.search"></label>
<input (keyup)="loadAllIfEmpty($event)" [(ngModel)]="currentSearch" class="form-control"
id="currentSearch" name="currentSearch"
type="text">
<button (click)="search(currentSearch)" class="input-group-append btn btn-info">
<fa-icon [icon]="'search'"></fa-icon>
</button>
<button (click)="clear()" *ngIf="currentSearch" class="input-group-append btn btn-danger">
<fa-icon [icon]="'trash-alt'"></fa-icon>
</button>
</div>
</form>
</div>
</div>
<br/>
<div *ngIf="tags?.length === 0" class="alert alert-warning">
<span jhiTranslate="aniplayApp.tag.home.notFound">No tags found</span>
</div>
<div *ngIf="tags?.length > 0" class="table-responsive">
<table aria-describedby="page-heading" class="table table-striped">
<thead>
<tr [(ascending)]="reverse" [(predicate)]="predicate" [callback]="transition.bind(this)" jhiSort>
<th jhiSortBy="id" scope="col"><span jhiTranslate="global.field.id">ID</span>
<fa-icon [icon]="'sort'"></fa-icon>
</th>
<th jhiSortBy="description" scope="col"><span
jhiTranslate="aniplayApp.tag.description">Description</span>
<fa-icon [icon]="'sort'"></fa-icon>
</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let tag of tags ;trackBy: trackId">
<td><a [routerLink]="['/tag', tag.id, 'view' ]">{{tag.id}}</a></td>
<td>{{tag.description}}</td>
<td class="text-right">
<div class="btn-group">
<button [routerLink]="['/tag', tag.id, 'view' ]"
class="btn btn-info btn-sm"
type="submit">
<fa-icon [icon]="'eye'"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="entity.action.view">View</span>
</button>
<button [routerLink]="['/tag', tag.id, 'edit']"
class="btn btn-primary btn-sm"
type="submit">
<fa-icon [icon]="'pencil-alt'"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="entity.action.edit">Edit</span>
</button>
<button (click)="delete(tag)" class="btn btn-danger btn-sm"
type="submit">
<fa-icon [icon]="'times'"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="entity.action.delete">Delete</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div [hidden]="tags?.length === 0">
<div class="row justify-content-center">
<jhi-item-count [itemsPerPage]="itemsPerPage" [page]="page" [total]="totalItems"></jhi-item-count>
</div>
<div class="row justify-content-center">
<ngb-pagination (pageChange)="loadPage($event)" [(page)]="page" [boundaryLinks]="true"
[collectionSize]="totalItems" [maxSize]="5" [pageSize]="itemsPerPage"
[rotate]="true"></ngb-pagination>
</div>
</div>
</div>
I still have some problem on the page reload. The pagination component is stuck on page 1 but I'm checking it
Edit: the only workaround I found is the same in the issue reported by @kaidohallik
I can confirm that on reload after a search action, which sets the url as something like
http://localhost:9000/tag;search=home;page=0;sort=id,asc
the parameters are not found obv.
Instead using a url with query params like
http://localhost:9000/tag?page=2&size=3&search=&sort=id,asc
it is working fine
@ChainlessWEB Can you test with the changes proposed in https://github.com/jhipster/generator-jhipster/pull/10959
I made PR #10959 to handle problems and suspicions described in https://github.com/jhipster/generator-jhipster/issues/10931#issuecomment-565909336
tag.component.ts file
import { Component, OnInit, OnDestroy } from '@angular/core';
import { HttpHeaders, HttpResponse } from '@angular/common/http';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { JhiEventManager } from 'ng-jhipster';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ITag } from 'app/shared/model/tag.model';
import { TagService } from './tag.service';
import { TagDeleteDialogComponent } from './tag-delete-dialog.component';
@Component({
selector: 'jhi-tag',
templateUrl: './tag.component.html'
})
export class TagComponent implements OnInit, OnDestroy {
tags?: ITag[];
eventSubscriber?: Subscription;
currentSearch: string;
totalItems = 0;
itemsPerPage = 2;
page!: number;
predicate!: string;
ascending!: boolean;
ngbPaginationPage = 1;
constructor(
protected tagService: TagService,
protected activatedRoute: ActivatedRoute,
protected router: Router,
protected eventManager: JhiEventManager,
protected modalService: NgbModal
) {
this.currentSearch =
this.activatedRoute.snapshot && this.activatedRoute.snapshot.queryParams['search']
? this.activatedRoute.snapshot.queryParams['search']
: '';
}
loadPage(page?: number): void {
if (!page) {
page = this.page;
}
if (this.currentSearch) {
this.tagService
.search({
page: page - 1,
query: this.currentSearch,
size: this.itemsPerPage,
sort: this.sort()
})
.subscribe((res: HttpResponse) => this.onSuccess(res.body, res.headers, page), () => this.onError());
return;
}
this.tagService
.query({
page: page - 1,
size: this.itemsPerPage,
sort: this.sort()
})
.subscribe((res: HttpResponse) => this.onSuccess(res.body, res.headers, page), () => this.onError());
}
search(query: string): void {
this.currentSearch = query;
this.loadPage(1);
}
ngOnInit(): void {
this.activatedRoute.data.subscribe(data => {
this.page = data.pagingParams.page;
this.ascending = data.pagingParams.ascending;
this.predicate = data.pagingParams.predicate;
this.ngbPaginationPage = data.pagingParams.page;
this.loadPage();
});
this.registerChangeInTags();
}
ngOnDestroy(): void {
if (this.eventSubscriber) {
this.eventManager.destroy(this.eventSubscriber);
}
}
trackId(index: number, item: ITag): number {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return item.id!;
}
registerChangeInTags(): void {
this.eventSubscriber = this.eventManager.subscribe('tagListModification', () => this.loadPage());
}
delete(tag: ITag): void {
const modalRef = this.modalService.open(TagDeleteDialogComponent, { size: 'lg', backdrop: 'static' });
modalRef.componentInstance.tag = tag;
}
sort(): string[] {
const result = [this.predicate + ',' + (this.ascending ? 'asc' : 'desc')];
if (this.predicate !== 'id') {
result.push('id');
}
return result;
}
protected onSuccess(data: ITag[] | null, headers: HttpHeaders, page: number): void {
this.totalItems = Number(headers.get('X-Total-Count'));
this.page = page;
this.ngbPaginationPage = this.page;
this.router.navigate(['/tag'], {
queryParams: {
page: this.page,
size: this.itemsPerPage,
search: this.currentSearch,
sort: this.predicate + ',' + (this.ascending ? 'asc' : 'desc')
}
});
this.tags = data ? data : [];
}
protected onError(): void {
this.ngbPaginationPage = this.page;
}
}
and tag.component.html file
<div>
<h2 id="page-heading">
<span jhiTranslate="jhipsterApp.tag.home.title">Tags</span>
<button id="jh-create-entity" class="btn btn-primary float-right jh-create-entity create-tag" [routerLink]="['/tag/new']">
<fa-icon [icon]="'plus'"></fa-icon>
<span class="hidden-sm-down" jhiTranslate="jhipsterApp.tag.home.createLabel">
Create a new Tag
</span>
</button>
</h2>
<jhi-alert-error></jhi-alert-error>
<jhi-alert></jhi-alert>
<div class="row">
<div class="col-sm-12">
<form name="searchForm" class="form-inline">
<div class="input-group w-100 mt-3">
<input type="text" class="form-control" [(ngModel)]="currentSearch" id="currentSearch" name="currentSearch" placeholder="{{ 'jhipsterApp.tag.home.search' | translate }}">
<button class="input-group-append btn btn-info" (click)="search(currentSearch)">
<fa-icon [icon]="'search'"></fa-icon>
</button>
<button class="input-group-append btn btn-danger" (click)="search('')" *ngIf="currentSearch">
<fa-icon [icon]="'trash-alt'"></fa-icon>
</button>
</div>
</form>
</div>
</div>
<br/>
<div class="alert alert-warning" *ngIf="tags?.length === 0">
<span jhiTranslate="jhipsterApp.tag.home.notFound">No tags found</span>
</div>
<div class="table-responsive" *ngIf="tags?.length > 0">
<table class="table table-striped" aria-describedby="page-heading">
<thead>
<tr jhiSort [(predicate)]="predicate" [(ascending)]="ascending" [callback]="loadPage.bind(this)">
<th scope="col" jhiSortBy="id"><span jhiTranslate="global.field.id">ID</span> <fa-icon [icon]="'sort'"></fa-icon></th>
<th scope="col" jhiSortBy="description"><span jhiTranslate="jhipsterApp.tag.description">Description</span> <fa-icon [icon]="'sort'"></fa-icon></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let tag of tags ;trackBy: trackId">
<td><a [routerLink]="['/tag', tag.id, 'view' ]">{{tag.id}}</a></td>
<td>{{tag.description}}</td>
<td class="text-right">
<div class="btn-group">
<button type="submit"
[routerLink]="['/tag', tag.id, 'view' ]"
class="btn btn-info btn-sm">
<fa-icon [icon]="'eye'"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="entity.action.view">View</span>
</button>
<button type="submit"
[routerLink]="['/tag', tag.id, 'edit']"
class="btn btn-primary btn-sm">
<fa-icon [icon]="'pencil-alt'"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="entity.action.edit">Edit</span>
</button>
<button type="submit" (click)="delete(tag)"
class="btn btn-danger btn-sm">
<fa-icon [icon]="'times'"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="entity.action.delete">Delete</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="tags?.length > 0">
<div class="row justify-content-center">
<jhi-item-count [page]="page" [total]="totalItems" [itemsPerPage]="itemsPerPage"></jhi-item-count>
</div>
<div class="row justify-content-center">
<ngb-pagination [collectionSize]="totalItems" [(page)]="ngbPaginationPage" [pageSize]="itemsPerPage" [maxSize]="5" [rotate]="true" [boundaryLinks]="true" (pageChange)="loadPage($event)"></ngb-pagination>
</div>
</div>
</div>
(res: HttpResponse) is missing the type.
Now I'm testing the behavior but I think the ITEMS_PER_PAGEconstant would be useful
Sorry, I tested and replaced itemsPerPage = ITEMS_PER_PAGE; with itemsPerPage = 2; - you can put wanted page size here instead of 2.
You can also checkout this branch and generate new app with this branch.
Already tested. It looks pretty good
Sorry, I tested and replaced
itemsPerPage = ITEMS_PER_PAGE;withitemsPerPage = 2;- you can put wanted page size here instead of 2.
Yes I was thinking about having it as default in the generator itself