У меня проблема с таблицей угловых материалов ( Таблица угловых материалов )
Я запустил ng generate @angular/material:material-table --name=car-table
, чтобы сгенерировать таблицу углов по умолчанию, которая работает нормально.Но если я пытаюсь ввести данные (автомобили) в CarsTableDataSource
, он перестает работать.Это должно быть что-то, связанное с асинхронными функциями и ngOnInit
хуками жизненного цикла.
Вы можете увидеть код в StackBlitz .Критическая часть находится в папке src/app/cars/
.
cars.component.ts
import {Component, OnInit, ViewChild} from '@angular/core';
import {Car} from '../car';
import {CarService} from '../car.service';
import {MatPaginator, MatSort, MatTable} from '@angular/material';
import {CarsTableDataSource} from './cars-table-datasource';
@Component({
selector: 'app-cars',
templateUrl: './cars.component.html',
styleUrls: ['./cars.component.css']
})
export class CarsComponent implements OnInit {
cars: Car[];
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
@ViewChild(MatTable) table: MatTable<Car>;
dataSource: CarsTableDataSource;
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['id', 'name', 'img_url'];
constructor(private carService: CarService) {
}
async ngOnInit() {
console.log('before getting cars: ');
console.log(this.cars);
this.cars = await this.carService.getCars().toPromise();
console.log('got cars:');
console.log(this.cars);
this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
}
add(name: string) {
name = name.trim();
if (!name) {
return;
}
this.carService.addCar({name} as Car)
.subscribe(car => {
this.cars = [...this.cars, car];
console.log(this.cars);
console.log('rendering rows');
this.table.renderRows();
});
}
delete(car: Car) {
this.cars = this.cars.filter(c => c !== car);
this.carService.deleteCar(car).subscribe();
this.table.renderRows();
}
}
cars-table-datasource.ts
import {DataSource} from '@angular/cdk/collections';
import {MatPaginator, MatSort} from '@angular/material';
import {map} from 'rxjs/operators';
import {merge, Observable, of as observableOf} from 'rxjs';
import {Car} from '../car';
/**
* Data source for the CarsTable view. This class should
* encapsulate all logic for fetching and manipulating the displayed cars
* (including sorting, pagination, and filtering).
*/
export class CarsTableDataSource extends DataSource<CarsTableItem> {
// cars: CarsTableItem[];
constructor(private paginator: MatPaginator, private sort: MatSort, public cars: Car[]) {
super();
}
/**
* Connect this cars source to the table. The table will only update when
* the returned stream emits new items.
* @returns A stream of the items to be rendered.
*/
connect(): Observable<CarsTableItem[]> {
// Combine everything that affects the rendered cars into one update
// stream for the cars-table to consume.
const dataMutations = [
observableOf(this.cars),
this.paginator.page,
this.sort.sortChange
];
// Set the paginator's length
this.paginator.length = this.cars.length;
return merge(...dataMutations).pipe(map(() => {
return this.getPagedData(this.getSortedData([...this.cars]));
}));
}
/**
* Called when the table is being destroyed. Use this function, to clean up
* any open connections or free any held resources that were set up during connect.
*/
disconnect() {
}
/**
* Paginate the cars (client-side). If you're using server-side pagination,
* this would be replaced by requesting the appropriate cars from the server.
*/
private getPagedData(data: CarsTableItem[]) {
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
return data.splice(startIndex, this.paginator.pageSize);
}
/**
* Sort the cars (client-side). If you're using server-side sorting,
* this would be replaced by requesting the appropriate cars from the server.
*/
private getSortedData(data: CarsTableItem[]) {
if (!this.sort.active || this.sort.direction === '') {
return data;
}
return data.sort((a, b) => {
const isAsc = this.sort.direction === 'asc';
switch (this.sort.active) {
case 'name':
return compare(a.name, b.name, isAsc);
case 'id':
return compare(+a.id, +b.id, isAsc);
default:
return 0;
}
});
}
}
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
function compare(a, b, isAsc) {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
cars.component.html
<div>
<label>Car name:
<input #carName />
</label>
<!-- (click) passes input value to add() and then clears the input -->
<button (click)="add(carName.value); carName.value=''">
add
</button>
</div>
<h2>My Cars</h2>
<div class="mat-elevation-z8 centered-table-div">
<table mat-table class="full-width-table" [dataSource]="dataSource" matSort aria-label="Elements">
<!-- Image Column -->
<ng-container matColumnDef="img_url">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Image</th>
<td mat-cell *matCellDef="let row">
<img [src]="row.img_url" alt="car image" class="car-image"/>
</td>
</ng-container>
<!-- Id Column -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
<td mat-cell *matCellDef="let row">{{row.id}}</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let row">{{row.name}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #paginator
[length]="dataSource.cars.length"
[pageIndex]="0"
[pageSize]="5"
[pageSizeOptions]="[3, 5, 25, 50]">
</mat-paginator>
</div>
Проблема заключается в ngOnInit
и
<mat-paginator #paginator
[length]="dataSource.cars.length"
[pageIndex]="0"
[pageSize]="5"
[pageSizeOptions]="[3, 5, 25, 50]">
</mat-paginator>
Как ошибкаЯ получаю ERROR TypeError: Cannot read property 'cars' of undefined
, что означает, что dataSource
не определен при разборе шаблона, но функция ngOnInit
:
async ngOnInit() {
console.log('before getting cars: ');
console.log(this.cars);
this.cars = await this.carService.getCars().toPromise();
console.log('got cars:');
console.log(this.cars);
this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
}
печатает:
На странице все еще загружается, но я не могу, например, добавить автомобили с помощью метода, потому что они добавляют в базу данных, но не обновляютв представлении, несмотря на вызов this.table.renderRows()
, как указано в документации:
Поскольку таблица оптимизируется по производительности, она не будет автоматически проверять изменения в массиве данных.Вместо этого, когда объекты добавляются, удаляются или перемещаются в массив данных, вы можете инициировать обновление отображаемых строк таблицы, вызывая ее метод renderRows()
.
Я попытался сделать ngOnInit
используйте Observable
s вместо async/await
, но это тоже не сработало:
ngOnInit() {
console.log('before getting cars: ');
console.log(this.cars);
this.carService.getCars().subscribe(cars => {
this.cars = cars;
console.log('got cars:');
console.log(this.cars);
this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
});
}
Если я не буду выполнять какие-либо операции извлечения из базы данных в ngOnInit
, тогда не будет 't никаких ошибок.
Я также не могу добавить автомобили с add()
прямо сейчас, как было упомянуто выше.
Если вам нужна какая-либо другая информация - не стесняйтесь спрашивать меня, и я позабочусьответить, как только смогу.
Редактировать
Если я отредактирую код, чтобы он выглядел следующим образом:
async ngOnInit() {
console.log('before getting cars: ');
console.log(this.cars);
console.log('got cars:');
this.cars = await this.carService.getCars().toPromise();
console.log(this.cars);
this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
}
Порядок ошибок изменится на:
, что означает, что ошибка происходит в
this.cars = await this.carService.getCars().toPromise();
Я уже пробовал с.subscribe()
и делать все в этом блоке, но не повезло.
Редактировать 2
Как указано в здесь (stackoverflow) вынужно инициализировать dataSource
пустым объектом, потому что представление разбирается до того, как завершатся все микрозадачи в ngOnInit
.Инициализировать paginator после представления init.
async ngOnInit() {
this.dataSource = new CarsTableDataSource(this.paginator, this.sort, []);
console.log('before getting cars: ');
console.log(this.cars);
this.cars = await this.carService.getCars().toPromise();
console.log('got cars:');
console.log(this.cars);
this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
}
Теперь это работает, но это своего рода хак.Я не знаю почему, но всякий раз, когда асинхронный код находится в хуке жизненного цикла в Angular, хук заканчивается до завершения асинхронного кода.Я не знаю почему.После того, как он видит await
, он немедленно выходит из функции, и только после этого dataSource
инициализируется.Я был бы очень признателен за объяснение.
Редактировать 3
Еще один обходной путь - добавить условно-нулевой оператор в представление, где оно разбивается следующим образом:
<mat-paginator #paginator
[length]="dataSource?.cars.length"
[pageIndex]="0"
[pageSize]="5"
[pageSizeOptions]="[3, 5, 25, 50]">
</mat-paginator>
С этой строкой:
[length]="dataSource?.cars.length"
Поскольку представление выполняется, когда ngOnInit наполовину завершено, вы должны добавлять его везде, где используете это свойство, чтобы оно не попадало в окончательный HTML-код при разборе представления.
Редактировать 4
Я обновил ссылку на приложение Stackblitz, теперь он настолько минималистичен, насколько это возможно, для представления проблемы.