如何为 Angular 中的表格正确实施 ngx-pagination?

How to properly implement ngx-pagination for tables in Angular?

我使用 ngx-pagination 包在我的 table 中使用 Angular 进行分页 7. 我为我的 ngFor* 添加了正确的指令和管道运算符,它迭代从提供的数据我创建的 SQL 服务。

分页控件正确显示了我想要的项目数(15)并创建了正确的页数...但是当我使用控件单击例如第 2 页时,table 不要改变。可见的项目始终是我的 'loadedTableData' 数组中的前 15 个。我还缺少一个额外的步骤吗?或者也许是因为我在原始 ngFor* 中使用了嵌套的 ngFor* 这不知何故破坏了分页?有人以前看过这个吗?

数据-table.html:

 <div id="table" class="table-editable">
      <div class="table-container">
        <div class="add-row-container">
          <form #addRowForm="ngForm" class="add-row-form"
            (ngSubmit)="addRow(addRowForm)">
            <table #addRowTable class="table
              table-bordered table-responsive-md table-striped
              text-center">
              <thead>
                <tr>
                  <th *ngFor="let head of loadedTableData[0] | keys;">{{head}}</th>
                </tr>
              </thead>
              <tr #newRowTemplate>
                <td *ngFor="let property of loadedTableData[0] | keys;"
                  class="form-group">
                  <input #prop ngModel
                    required class="form-control" contenteditable="true"
                    name="{{property}}">
                </td>
              </tr>
            </table>
            <div class="buttons-container">
              <button class="btn-success rounded btn-sm my-0 btn"
                type="submit"
                [disabled]="!addRowForm.valid">Add Row</button>
              <button class="btn-outline-primary rounded btn-sm my-0 btn"
                type="button"
                (click)="addRowForm.resetForm()">Clear</button>
            </div>
          </form>
        </div>
        <div class="table-container">
          <form #updateRowForm="ngForm" class="update-row-form">
            <table #tableEl="" class="table table-bordered
              table-responsive-md table-striped text-center">
              <thead>
                <!-- <tr>
                  <nav class="navbar">
                    <input class="form-control" type="text" name="search"
                      [(ngModel)]="filter">
                  </nav>
                </tr> -->
                <tr>
                  <th> Row </th>
                  <th *ngFor="let head of loadedTableData[0] | keys;">{{head}}</th>
                </tr>
              </thead>
              <tbody>
                <tr *ngFor="let item of loadedTableData
                            | paginate: {
                                id: 'ltd',
                                itemsPerPage: 10,
                                currentPage: p,
                                totalItems: total
                              };
                          let i= index;"
                  (click)="updatePreviousValue(item);">
                  <td class="form-group" #rowIndex>
                    <span> {{ i + 1 }} </span>
                  </td>
                  <td *ngFor="let property of item | keys;"
                    class="form-group" #editRow>
                    <input #editRowProp
                      [(ngModel)]="loadedTableData[i][property]"
                      class="form-control"
                      [name]="property + '_' + i"
                      type="text">
                  </td>
                  <td>
                    <button type="button" class="btn btn-primary
                      rounded
                      btn-sm my-0"
                      (click)="updateRow(loadedTableData[i])">Update</button>
                    <hr>
                    <button type="button" class="btn btn-danger
                      rounded
                      btn-sm my-0" (click)="deleteRow(item)">Remove</button>
                  </td>
                </tr>
              </tbody>
              <tfoot id="pagination-control-container">
                <tr>
                  <td [colSpan]="99">
                    <pagination-controls
                      (pageChange)="pageChange($event);"
                      id='ltd'>
                    </pagination-controls>
                  </td>
                </tr>
              </tfoot>
            </table>
          </form>
        </div>

      </div>
    </div>

数据-table.component.ts:

import { Component, OnInit, ViewChild, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef, Input } from '@angular/core';
import { SqlService } from '../services/sql.service';
import { NgForm, FormGroup } from '@angular/forms';
import { Subscription, BehaviorSubject } from 'rxjs';
import { MatSnackBar } from '@angular/material';
import { SuccessComponent } from '../snackbar/success/success.component';
import { ErrorComponent } from '../snackbar/error/error.component';
import { ConfirmComponent } from '../snackbar/confirm/confirm.component';

@Component({
selector: 'app-data-table',
templateUrl: './data-table.component.html',
styleUrls: ['./data-table.component.scss']
})
export class DataTableComponent implements OnInit, OnDestroy {

constructor(
    private sqlService: SqlService,
    private snackBar: MatSnackBar,
    private cdRef: ChangeDetectorRef) { }
@ViewChild('addRowForm') addRowForm: NgForm;
@ViewChildren('prop') addRowProps: QueryList<any>;
@ViewChild('editRowForm') editRowForm: NgForm;
@ViewChild('editRow') editRow: FormGroup;

@Input() id: string;
@Input() maxSize: number;

public loadedTableData: any = [];
public previousTableData: object[] = [];
public displayedColumns: object[] = [];
public tableHasBeenLoaded = false;
public rowBeingEdited: BehaviorSubject<any> = new BehaviorSubject<any>({});
public rowPreviousValue = {};
public currentTableData: object = {};
public rowsAffected = 0;

private subscriptions: Subscription[] = [];

public p = 1;

public pageChange(event: number): void {
    this.p = event;
}

public ngOnInit(): void {
    this.subscriptions.push(
    this.sqlService.tableHasBeenLoaded.subscribe(data => {
        this.tableHasBeenLoaded = data;
    }),
    this.sqlService.currentDataView.subscribe(data => {
        this.loadedTableData = data;
        if (data.length > 0) {
        this.displayedColumns.push(Object.getOwnPropertyNames(data[0]));
        }
    }),
    this.sqlService.tableHasBeenLoaded.subscribe(data => {
        this.tableHasBeenLoaded = data;
    }),
    this.sqlService.currentTableData.subscribe(data => {

        this.cdRef.detectChanges();

        this.currentTableData = data;

    }),
    this.sqlService.rowsAffected.subscribe(data => {
        this.rowsAffected = data;
    })
    );
}

public addRow(addRowData: NgForm): void {
    const newDataValues = [];
    const loadedValues = {};
    let newDataKeys: object;

    const tableData = {
    row: addRowData.value,
    currentTableData: this.currentTableData
    };

    this.subscriptions.push(
    this.sqlService.insertTableData(tableData)
        .subscribe((resp) => {
        if (resp) {

            for (const prop of this.addRowProps.toArray()) {
            newDataValues.push(prop['nativeElement'].value as HTMLInputElement);
            }

            newDataKeys = Object.keys(addRowData.controls);

            Object.assign(newDataKeys).map((key, i) => {
            loadedValues[key] = newDataValues[i];
            });

            if (this.loadedTableData.length > 0) {
            const newRow = loadedValues;
            this.loadedTableData.push(newRow);
            }

            this.snackBar.openFromComponent(SuccessComponent, {
            duration: 3000,
            data: `${this.rowsAffected} row(s) added.`
            });

            this.addRowForm.resetForm();
        }
        }, (err) => {
        this.snackBar.openFromComponent(ErrorComponent, {
            duration: 6000,
            data: `${err}`
        });
        })
    );

}

public updatePreviousValue(item: object): void {
    this.rowPreviousValue = JSON.parse(JSON.stringify(item));
}

public updateRow(newRowValue: object): void {

    const previousRowValue = this.rowPreviousValue;

    const updateData = {
    previousRowValue,
    newRowValue
    };

    this.subscriptions.push(
    this.sqlService.updateTableData(updateData)
        .subscribe((resp) => {
        if (resp) {
            this.snackBar.openFromComponent(ConfirmComponent, {
            duration: 3000,
            data: `${this.rowsAffected} row(s) updated.`
            });
        }
        }, (err) => {
        this.snackBar.openFromComponent(ErrorComponent, {
            duration: 6000,
            data: `${err}`
        });
        })
    );

}

public deleteRow(item: object): void {

    const tableData = {
    row: item,
    currentTableData: this.currentTableData
    };

    this.subscriptions.push(
    this.sqlService.deleteTableData(tableData)
        .subscribe((resp) => {
        if (resp) {
            this.loadedTableData = this.loadedTableData.filter(obj => obj !== item);

            this.snackBar.openFromComponent(ErrorComponent, {
            duration: 3000,
            data: `${this.rowsAffected} row(s) deleted.`
            });
        }
        }, (err) => {
        this.snackBar.openFromComponent(ErrorComponent, {
            duration: 6000,
            data: `${err}`
        });
        })
    );
}

public ngOnDestroy(): void {
    for (const sub of this.subscriptions) {
      sub.unsubscribe();
    }
  }

}

sql.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { tap, catchError, delay } from 'rxjs/operators';
import { Observable, BehaviorSubject, of, throwError } from 'rxjs';
import { ITableList } from '../interfaces/ITableList.interface';
import { MatSnackBar } from '@angular/material';
import { ErrorComponent } from '../snackbar/error/error.component';

@Injectable({
  providedIn: 'root',
})
export class SqlService {

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { tap, catchError, delay } from 'rxjs/operators';
import { Observable, BehaviorSubject, of, throwError } from 'rxjs';
import { ITableList } from '../interfaces/ITableList.interface';
import { MatSnackBar } from '@angular/material';
import { ErrorComponent } from '../snackbar/error/error.component';

@Injectable({
providedIn: 'root',
})
export class SqlService {

private uri = 'http://localhost:8080';
private headers = new HttpHeaders({ 'Content-Type': 'application/json; charset=utf-8' });

public currentDataView: BehaviorSubject<any> = new BehaviorSubject<any>([]);
public currentTableData: BehaviorSubject<any> = new BehaviorSubject<any>({});
public tableHasBeenLoaded: BehaviorSubject<any> = new BehaviorSubject<boolean>(false);
public rowsAffected: BehaviorSubject<number> = new BehaviorSubject<number>(0);

constructor(private http: HttpClient,
            private snackBar: MatSnackBar) { }

public getMasterDBList(): Observable<any> {
    return this.http.get<ITableList>(`${this.uri}/api/masterDBList`)
    .pipe(
        tap(
        response => {
            console.log(response);
        }
        )
    );
}

public deleteTableData(tableRow: any): Observable<any> {
    const parsedData = JSON.parse(JSON.stringify(tableRow));

    if (tableRow) {
    return this.http.post<any>(`${this.uri}/api/deleteTableData`, parsedData).pipe(
        tap(
        response => {
            this.rowsAffected.next(response.rowsAffected);
        }
        ),
        catchError(this.handleError)
    );
    }
}

public insertTableData(tableData: any): Observable<any> {
    const parsedData = JSON.parse(JSON.stringify(tableData));

    if (tableData.row) {
    return this.http.post<any>(`${this.uri}/api/insertTableData`, parsedData).pipe(
        tap(
        response => {
            this.rowsAffected.next(response.rowsAffected);
        }
        ),
        catchError(this.handleError)
    );
    }
}

public updateTableData(updateData: any): Observable<any> {
    const parsedUpdateData = JSON.parse(JSON.stringify(updateData));

    const parsedData = {
    currentTableData: this.currentTableData.getValue(),
    parsedUpdateData
    };

    if (updateData) {
    return this.http.post<any>(`${this.uri}/api/updateTableData`, parsedData).pipe(
        tap(
        response => {
            this.rowsAffected.next(response.rowsAffected);
        }
        ),
        catchError(this.handleError)
    );
    }
}

public getTableData(tableData?: any): Observable<any> {
    // clear currentDataView so that load icon appears between queries.
    this.currentDataView.next([]);

    // for state purposes, subscribers are notified that a GET has been called in this session.
    this.tableHasBeenLoaded.next(true);

    const parsedData = JSON.parse(JSON.stringify(tableData));

    return this.http.get<object[]>(`${this.uri}/api/getTableData`, {
    params: parsedData,
    headers: this.headers
    })
    .pipe(
        tap(
        response => {
            this.currentDataView.next(response);
            console.log(this.currentDataView.getValue());
        }
        ),
        catchError(this.handleError)
    );
}

public handleError(errorResponse: HttpErrorResponse): Observable<any> {

    if (errorResponse.error instanceof ErrorEvent) {
        console.error('Client Side Error: ', errorResponse.error.message);
    } else {
        console.error('Server Side Error: ', errorResponse);
    }

    return throwError(errorResponse.error.message);
  }
}

第 1 页结果:

第 2 页结果:

所以,我知道发生了什么。在我的 ngFor 循环中,我根据数组中的索引绑定模型。问题是,索引将始终为 0-9,因为这是在模板中呈现的内容。

意味着我的 ngModels 将始终在每次查看新页面时使用 myPropertyName_0 - myPropertyName_9,为呈现的每个页面一遍又一遍地绑定相同的 10 个模型。

修复很简单,我没有使用索引,而是使用了我从父元素中的 ngFor 循环创建 'item' 的局部变量来访问该行数据的 REAL 模型。

旧实现:

        <tbody>
        <tr *ngFor="let item of loadedTableData
                    | paginate: {
                        id: 'ltd',
                        itemsPerPage: 10,
                        currentPage: page
                        };
                    let i= index;"
            (click)="updatePreviousValue(item);">
            <td class="form-group" #rowIndex>
            <span> {{ i + 1 }} </span>
            </td>
            <td *ngFor="let property of item | keys;"
            class="form-group" #editRow>
            <input #editRowProp
                <!-- WRONG MODEL BEING BOUND -->
                **[(ngModel)]="loadedTableData[i][property]"**
                class="form-control"
                [name]="property + '_' + i"
                type="text">
            </td>
            <td>
            <button type="button" class="btn btn-primary
                rounded
                btn-sm my-0"
                (click)="updateRow(loadedTableData[i])">Update</button>
            <hr>
            <button type="button" class="btn btn-danger
                rounded
                btn-sm my-0" (click)="deleteRow(item)">Remove</button>
            </td>
        </tr>
    </tbody>

固定码:

        <tbody>
        <tr *ngFor="let item of loadedTableData
                    | paginate: {
                        id: 'ltd',
                        itemsPerPage: 10,
                        currentPage: page
                        };
                    let i= index;"
            (click)="updatePreviousValue(item);">
            <td class="form-group" #rowIndex>
            <span> {{ i + 1 }} </span>
            </td>
            <td *ngFor="let property of item | keys;"
            class="form-group" #editRow>
            <input #editRowProp
                <!-- CORRECT MODEL ! -->
                **[(ngModel)]="item[property]"**
                class="form-control"
                [name]="property + '_' + i"
                type="text">
            </td>
            <td>
            <button type="button" class="btn btn-primary
                rounded
                btn-sm my-0"
                (click)="updateRow(loadedTableData[i])">Update</button>
            <hr>
            <button type="button" class="btn btn-danger
                rounded
                btn-sm my-0" (click)="deleteRow(item)">Remove</button>
            </td>
        </tr>
    </tbody>