Angular Material MatPaginator showing page 0 of 0 - angular-material

sorry for the question in advance. I googled my problem before writing this and tried lots of solutions, but without luck.
I'm an Angular noob trying to learn Material.
My simple project makes a http request and uses the JSON received to populate a table.
The Angular Material table is working fine, but the paginator is not.
It is greyed and shows page 0 of 0.
The component.html is as follows:
<div style="height: 80vh;">
<mat-toolbar color="primary">
<mat-toolbar-row>
<button mat-icon-button (click)="sidenav.toggle()" fxShow="true" fxHide.gt-sm>
<mat-icon>menu</mat-icon>
</button>
<span>Teste - Requisição Http/Tabela</span>
<span class="menu-spacer"></span>
</mat-toolbar-row>
</mat-toolbar>
<mat-sidenav-container fxFlexFill>
<mat-sidenav #sidenav>
<mat-nav-list>
<a (click)="sidenav.toggle()" mat-list-item>Fechar</a>
<a href="#" mat-list-item>Link 1</a>
<a href="#" mat-list-item>Link 2</a>
<a href="#" mat-list-item>Link 3</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content fxFlexFill>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef> Id </th>
<td mat-cell *matCellDef="let element"> {{element.id}} </td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef> Email </th>
<td mat-cell *matCellDef="let element"> {{element.email}} </td>
</ng-container>
<ng-container matColumnDef="first_name">
<th mat-header-cell *matHeaderCellDef> First Name </th>
<td mat-cell *matCellDef="let element"> {{element.first_name}} </td>
</ng-container>
<ng-container matColumnDef="last_name">
<th mat-header-cell *matHeaderCellDef> Last Name </th>
<td mat-cell *matCellDef="let element"> {{element.last_name}} </td>
</ng-container>
<ng-container matColumnDef="avatar">
<th mat-header-cell *matHeaderCellDef> Avatar </th>
<td mat-cell *matCellDef="let element"> {{element.avatar}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]" showFirstLastButtons></mat-paginator>
</mat-sidenav-content>
</mat-sidenav-container>
</div>
The component.ts is as follows:
import { HttpClient } from '#angular/common/http';
import { AfterViewInit, Component, ViewChild } from '#angular/core';
import { MatPaginator } from '#angular/material/paginator';
import { MatTableDataSource } from '#angular/material/table';
export interface UserData {
id: number;
email: string;
first_name: string;
last_name: string;
avatar: string;
}
const USER_DATA: UserData[] = [];
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
title = 'Test';
url = 'https://reqres.in/api/users?page=1';
displayedColumns: string[] = ['id', 'email', 'first_name', 'last_name', 'avatar'];
dataSource = new MatTableDataSource<UserData>(USER_DATA);
constructor(private http: HttpClient) {
this.http.get(this.url).toPromise().then(data => {
console.log(data["data"]);
this.dataSource = data["data"]
console.log(this.dataSource)
});
}
#ViewChild(MatPaginator) paginator: MatPaginator;
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
}
}
What am I doing wrong?
Thanks

Try adding #paginator in
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]" showFirstLastButtons></mat-paginator>
It should be:
<mat-paginator #paginator [pageSizeOptions]="[5, 10, 25, 100]" showFirstLastButtons></mat-paginator>
Without the id "#paginator" the #ViewChild decorator will never be able to find the element (this id must be the same as the used in the #ViewChild decorator line).
This little detail is not included in the Pagination example in the Angular Material documentation (or I was not able to see it..)
Beside this, try declaring the dataSource object as:
dataSource! = MatTableDataSource<UserData>;
instead of
dataSource = new MatTableDataSource<UserData>(USER_DATA);
Then, when the promise is resolved, assign the contents this way
this.dataSource = new MatTableDataSource<UserData>(data.data);
instead of
this.dataSource = data["data"]
Immediately after that, assign the paginator component to the dataSource.paginator:
this.dataSource.paginator = this.paginator;

Try removing the AfterView and replace the ViewChild with the below code:
#ViewChild(MatPaginator) set matPaginator(paginator: MatPaginator) {
this.dataSource.paginator = paginator;
}

In the angular's documentation, they forgot to add, that you need to define { static: false} or {static: true} .
Like this:
#ViewChild(MatPaginator, {static:false}) paginator: MatPaginator;
in docs:
static - True to resolve query results before change detection runs,
false to resolve after change detection. Defaults to false.
You need to set it to false, if you want to use default.

I needed to add this.paginator.length = +data.meta.total; so that length prop of paginator knew the total possible items, and could calculate the pagination.
Adding, the example from ng docs doesn't show this and docs show as 'hard coded' values.
ng docs need work!

Related

I can't display a column from the dataSource in an angular material table

I have this dataSource
In my filtro component, I call to listado-proyectos component and I pass it dataSource
Filtro
<app-listado-proyectos [proyectos]="dataSource" [origen]="'certificados'"
*ngIf="dataSource.length>0"></app-listado-proyectos>
Listado Proyectos
#Component({
selector: 'app-listado-proyectos',
templateUrl: './listado-proyectos.component.html',
styleUrls: ['./listado-proyectos.component.scss']
})
export class ListadoProyectosComponent implements OnInit, OnChanges {
#Input()
proyectos: IProyecto[];
#Input()
origen: string;
dataSource: MatTableDataSource<IProyecto>
columnsToDisplay: string[] = [];
filtro: string;
#ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
constructor(private generalService: GeneralService) { }
ngOnChanges(changes: SimpleChanges): void {
this.columnsToDisplay = ['codigo', 'descripcionCorta', 'jefeGrupo', 'responsable',
'estado', 'cliente', 'fechaInicio', 'fechaFin', 'presupuesto', 'facturable',
'certificado'];
this.dataSource = new MatTableDataSource(this.proyectos);
console.log("Listado de proyectos en ListadoProyectosComponent", this.dataSource);
}
<mat-table [dataSource]="dataSource" matTableExporter #exporter="matTableExporter"
multiTemplateDataRows matSort class="mat-elevation-z8">
......
<ng-container matColumnDef="certificado" *ngIf="origen=='certificado'">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Certificado</th>
<td mat-cell *matCellDef="let element">{{element.certificado}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay; sticky:true"></tr>
<tr mat-row *matRowDef="let row; columns: columnsToDisplay;"></tr>
</mat-table>
in listado component I get the data
But I get this error
Any idea, please?
Thanks
<mat-table [dataSource]="dataSource" matTableExporter #exporter="matTableExporter" multiTemplateDataRows matSort
class="mat-elevation-z8">
.....
<ng-container matColumnDef="certificado" *ngIf="origen=='certificado'">
<th mat-header-cell *matHeaderCellDef>Certificado</th>
<td mat-cell *matCellDef="let element">{{element.certificado}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay; sticky:true"></tr>
<tr mat-row *matRowDef="let row; columns: columnsToDisplay;"></tr>
</mat-table>

Angular Material v13 Table not Sorting

I made an Angular Material Table and now I'm trying to implement Sort. I build the table following the documentation for version 13, but the sorting doesn't work (even though the header is clickable and the sorting arrow changes direction on click.
Here is my component:
import { Component, VERSION, ViewChild } from '#angular/core';
import { MatSort } from '#angular/material/sort';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
#ViewChild(MatSort, { static: true }) sort!: MatSort;
dataSource = [
{ value: 'One %value% none', type: 'Type is %type%', time: 1 },
{ value: 'Another %value%', type: 'This type is %type%', time: 3 },
{ value: '%value% none', type: 'The type is %type%', time: 2 },
];
displayedColumns: string[] = Object.keys(this.dataSource[0]);
name = 'Angular ' + VERSION.major;
}
And here is the html:
<p>Table works</p>
<table mat-table matSort [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Value Column -->
<ng-container matColumnDef="value">
<th mat-sort-header mat-header-cell *matHeaderCellDef>Vaule</th>
<td mat-cell *matCellDef="let element">{{ element.value }}</td>
</ng-container>
<!-- Type Column -->
<ng-container matColumnDef="type">
<th mat-sort-header mat-header-cell *matHeaderCellDef>Type</th>
<td mat-cell *matCellDef="let element">{{ element.type }}</td>
</ng-container>
<!-- Time Column -->
<ng-container matColumnDef="time">
<th mat-sort-header mat-header-cell *matHeaderCellDef>Time</th>
<td mat-cell *matCellDef="let element">{{ element.time }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
The full reproducible example can be found here:
stackblitz
ps: there are a few similar questions in SO, but I couldn't find one with this version and this specifications.

MatSort and MatPaginator not being initialized using #ViewChild and ngAfterViewInit if inside *ngIf

I am using a mat table with paginator and sort within an *ngIf. The table data is retrieved using an API.
This is the component's typescript code:
export class UserPermissionAdminComponent implements AfterViewInit, OnInit {
dataSource: MatTableDataSource<UserPermission> = new MatTableDataSource<UserPermission>();
columnsToDisplay: string[] = ['id', 'name'];
#ViewChild(MatSort, { static: false }) sort!: MatSort;
#ViewChild(MatPaginator, { static: false }) paginator!: MatPaginator;
constructor(private userPermService: UserPermissionService) {
}
ngOnInit(): void {
this.userPermService.getAllUserPermissions()
.subscribe(recvPerms => this.dataSource.data = recvPerms);
}
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
console.log(this.dataSource.sort);
}
applyFilter(event: Event): void {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();
}
}
And this is the component's template:
<div class="container">
<h2>Permisiuni utilizatori</h2>
<ng-container *ngIf="dataSource.data.length; then existingPermissions; else noPermissions">
</ng-container>
<ng-template #existingPermissions>
<div>
<div class="filter-container">
<mat-form-field>
<mat-label>Cautare</mat-label>
<input matInput (keyup)="applyFilter($event)" placeholder="Ex: READ" #input />
</mat-form-field>
</div>
<div class="table-container mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
<td mat-cell *matCellDef="let permission"> {{permission.id}} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Denumire permisiune </th>
<td mat-cell *matCellDef="let permission"> {{permission.name}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let rowData; columns: columnsToDisplay"></tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 20]"></mat-paginator>
</div>
</div>
</ng-template>
<ng-template #noPermissions>
<div>
<span class="notification">Nu a fost gasita nicio permisiune utilizator.</span>
</div>
</ng-template>
</div>
The issue is that sorting and pagination do not work (the console.log also lists that this.sort is undefined). The table contains all the data on a single page. Filtering also works.
I have managed to fix the issue by adding setters to the paginator and sort:
#ViewChild(MatSort, { static: false }) set sort(val: MatSort) {
if (val) {
this.dataSource.sort = val;
}
}
#ViewChild(MatPaginator, { static: false }) set paginator(val: MatPaginator) {
if (val) {
this.dataSource.paginator = val;
}
}
However, the code now works even without ngAfterViewInit. Can someone please explain how and why this works?

Unable to test data in Angular Material Data Table: TypeError: Cannot read property 'nativeElement' of null

I am writing unit test cases for Angular material table. Here's the template code snippet:
<table mat-table [dataSource]="dataSource" matSort>
<!-- Edit/Delete -->
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>
<div class="mat-table-header">Action</div>
</th>
<td mat-cell *matCellDef="let type">
<div class="row-action">
<button class="btn--delete" mat-icon-button aria-label="delete button" (click)="openDeleteDialog(type)">
<span class="icon-delete"></span>
</button>
<button class="btn--edit" mat-icon-button aria-label="edit button"
(click)="openAddOrEditDialog(category.TYPE, type)">
<span class="icon-edit"></span>
</button>
</div>
</td>
</ng-container>
<!-- Type Name -->
<ng-container matColumnDef="avtName">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="mat-table-header">Type</th>
<td mat-cell *matCellDef="let type">{{ type.avtName }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let type; columns: displayedColumns"></tr>
</table>
I am testing the openAddOrEditDialog which opens a MatDialog. I have inserted 2 records into the data table. When I try to test the clicking of the edit button by using fixture.debugElement.query(By.css(".btn--edit")), I always get null, meaning the records didn't get rendered in the data table, but, when I query the table using fixture.debugElement.query(By.css("table")), I can see the records in the console.log(fixture.debugElement.query(By.css("table"))).
Where am I going wrong?
Here's my test case:
it(`some description`, () => {
const spy = jest.spyOn(notificationService, "notifySuccess");
const editBtn: HTMLElement = fixture.debugElement.query(By.css(".btn--edit"))
.nativeElement;
editBtn.click();
expect(spy).toBeCalledWith("Type was successfully updated");
});
I got the answer.
We can not use fixture.debugElement.query for Angular Material Data Table, instead it works with fixture.debugElement.nativeElement.querySelector.
It is suggested in https://medium.com/#penghuili/angular-unit-test-cant-query-material-table-e2c25abc4937

Data-table don't show my DataSource Array

I have a problem that I do not know how to solve it from my component I send an array of Resource so that another component reads it and injects it into a DataSource that is read by a mat-table. Consulted the documentation of Angular, I see that it is correct. I do not know if it's something in the html that does not allow me to see it. Can someone tell me the reason?
Thank you so much
my .ts
#Input() resources : Resource [];
public dataSource = new MatTableDataSource <Resource> ();
public ngOnInit(): void {
console.log(this.resources)
this.dataSource.data = this.resources
}
my .html
<mat-table class="resources-table" #table [dataSource]="resources" matSort [#animateStagger]="{value:'50'}" fusePerfectScrollbar>
<!-- ID Column -->
<ng-container matColumnDef="_id">
<mat-header-cell *matHeaderCellDef mat-sort-header>ID</mat-header-cell>
<mat-cell *matCellDef="let resource">
<p class="text-truncate">{{resource._id}}</p>
</mat-cell>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header>{{'RESOURCE.NAME' | translate}}</mat-header-cell>
<mat-cell *matCellDef="let resource">
<p class="text-truncate">{{resource.name}}</p>
</mat-cell>
</ng-container>
</mat-table>
console.log(this.resources) is img:
Try to set DataSource property of Mat-Table in which you assinging the data:
HTML code:
\/ <-- Here
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
<!-- Position Column -->
<ng-container matColumnDef="_id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> No. </th>
<td mat-cell *matCellDef="let element"> {{element._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 element"> {{element.name}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
TS code:
import { Component, OnInit, ViewChild } from '#angular/core';
import { MatSort, MatTableDataSource } from '#angular/material';
#Component({
selector: 'table-sorting-example',
styleUrls: ['table-sorting-example.css'],
templateUrl: 'table-sorting-example.html',
})
export class TableSortingExample implements OnInit {
displayedColumns: string[] = ['_id', 'name'];
dataSource = new MatTableDataSource([]);
#ViewChild(MatSort) sort: MatSort;
ngOnInit() {
var elements = [{ _id: 1, name: 'Paul Walker' },
{ _id: 2, name: 'Lisa' }];
this.dataSource = new MatTableDataSource(elements); // Set dataSource like this
this.dataSource.sort = this.sort; // and then assign sort
}
}
Stackblitz

Resources