我正在使用Angular 7在表中使用ngx-pagination包进行分页。我为ngFor *添加了正确的指令和管道运算符,该指令和管道运算符可对从我创建的SQL Service提供的数据进行迭代。
分页控件正确显示了我想要的项目数(15),并创建了正确的页面数...但是,当我使用这些控件单击例如第2页时,表中的项目不会更改。可见的项目始终是我的“ loadedTableData”数组中的前15个项目。我还缺少其他步骤吗?还是因为我在原始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页结果:
第2页结果:
答案 0 :(得分:1)
所以,我知道发生了什么。在我的ngFor循环中,我基于数组中的索引来绑定模型。问题是,索引将始终为0-9,因为这就是模板中呈现的内容。
每次查看新页面时,我的ngModel都将始终使用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>