Как правильно реализовать ngx-пагинацию для таблиц в Angular? - PullRequest
1 голос
/ 20 июня 2019

Я использую пакет ngx-pagination для разбивки на страницы в своей таблице, используя Angular 7. Я добавил правильные директивы и операторы конвейера для моего ngFor *, который выполняет перебор данных, предоставленных из созданной мной службы SQL.

Элементы управления нумерацией страниц правильно отображают, сколько элементов я хочу (15), и создают правильное количество страниц ... тем не менее, когда я использую элементы управления, чтобы щелкнуть, например, на странице 2, элементы в таблице не отображаются.т изменить.Видимые элементы всегда являются первыми 15 из моего массива «loadedTableData ».Есть ли дополнительный шаг, который я пропускаю?Или, может быть, потому что я использую вложенный ngFor * в оригинальном ngFor *, это как-то портит нумерацию страниц?Кто-нибудь еще видел это раньше?

data-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>

data-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 Результаты:

page 1 results:

СТРАНИЦА 2 результаты:

page 2 results:

1 Ответ

1 голос
/ 24 июня 2019

Итак, я понял, что случилось. В моем цикле ngFor я связывал модели на основе индекса в массиве. Проблема в том, что индекс всегда будет 0-9, поскольку это то, что отображается в шаблоне.

Это означает, что мои ngModels всегда будут использовать myPropertyName_0 - myPropertyName_9 каждый раз, когда просматривается новая страница, связывая одни и те же 10 моделей снова и снова для каждой отображаемой страницы.

Исправление было простым, вместо того, чтобы использовать индекс, я использовал локальную переменную, которую я создал 'item' из цикла ngFor в родительском элементе, чтобы получить доступ к модели 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>
...