PrimeNG Table column sorting doesn't work - angular7

I have created an Angular application using PrimeNG Table. I've checked the PrimeNG Documentation here (https://www.primefaces.org/primeng/#/table/sort). The problem is that my table is not sorting. The sorting icons are showing up, and when I press any of them, the animation changes, but the data is not being sorted.
Here is my code for the component.ts
import { Component, OnDestroy, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { DownloadService } from '../services/download/download.service';
import { SelectItem, MultiSelectModule } from "primeng/primeng";
#Component({
selector: 'app-downloads',
templateUrl: './downloads.component.html',
styleUrls: ['./downloads.component.css'],
providers: [DownloadService]
})
export class DownloadsComponent implements OnInit {
downloadData: any[] = [];
cols: any[];
columnOptions: SelectItem[];
constructor(private http: HttpClient, private data: DownloadService) { }
ngOnInit() {
this.getData();
setInterval(() => {
this.getData();
}, 5000);
}
getData() {
this.data.GetDownloads().subscribe(data => {
this.downloadData = data;
});
}
}
and the code for component.html
<p-table [value]="downloadData" [rows]="10" [paginator]="true" sortMode="multiple" [rowsPerPageOptions]="[5,10,20]" [rowHover]="true">
<ng-template pTemplate="header">
<tr>
<th style="text-align: center;" [pSortableColumn]="'Logger Name'">Logger Name
<p-sortIcon [field]="'Logger Name'"></p-sortIcon>
</th>
<th style="text-align: center;" [pSortableColumn]="'Progress'">Progress
<p-sortIcon [field]="'Progress'"></p-sortIcon>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-download>
<tr>
<td style="text-align: center;">{{ download.logger.name }}</td>
<td style="text-align: center;">
<p-progressBar [value]="download.progress"></p-progressBar>
</td>
</tr>
</ng-template>
</p-table>

Apparently, I was missing the #dt [columns]="cols" directives in the p-table tag. Populating the cols field and adding the directives solved the problem.

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>

Updating Angular Material Table data and columns with ngrx and trackby does not refresh the DOM

I've got that Angular Material Table :
<table mat-table [dataSource]="data" [trackBy]="trackBy">
<ng-container
[matColumnDef]="column.value"
*ngFor="let column of columns; let i = index"
>
<th mat-header-cell *matHeaderCellDef>{{ column.label }}</th>
<td mat-cell *matCellDef="let element">
<input
matInput
[value]="element[column.value].value"
type="text"
(input)="updatedFn($event.target.value)"
/>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let row; columns: columnsToDisplay"></tr>
</table>
And this ts file :
#Component({
...
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StuffComponent implements OnInit, OnChanges {
#Input() data!: Data[];
#Input() columns!: Column[];
#Output() updated = new EventEmitter<Updated>();
columnsToDisplay!: string[];
constructor(private cdr: ChangeDetectorRef) {}
trackBy(index: number) {
return index;
}
ngOnChanges() {
this.cdr.markForCheck();
}
ngOnInit() {
this.columnsToDisplay = this._columns.map((item: Column) => item.value);
}
updatedFn(value: string) {
this.updated.emit(value);
}
}
I added the trackBy because I was losing focus on input whenever I'm typing. Now with trackBy, I can freely type, it dispatches the action, then I use the reducer to make the changes to the state, then it correctly send back the fresh data to the table component : the #Input() data/colums are corrects. However, it does not update the DOM. I assume this is because of the trackBy, but if I use something like this :
trackBy(index: number, stuff) {
return stuff.value;
}
...it loses focus again on the input.
Also, I tried to use this.cdr.markForCheck() but it did not do anything.
How can I update the DOM despite the trackyBy?
Edit: to be more precise on how I know it does not update the DOM -> in my reducer, I change the data value of another table cell but it does not appear on the table despite the #Input data contains the fresh data.
I found a solution: I added the same trackyBy to the ngFor itself :
<ng-container [matColumnDef]="column.value" *ngFor="let column of columns; let i = index; trackBy: trackBy"
I also removed the ngOnChanges and markForCheck()

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?

Angular Material MatPaginator showing page 0 of 0

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!

render: function - JSX failed to compilie with SyntaxError Unexpected token - Rails 5.1 Webpack Reactjs

I'm having an issue running a React component as I get a Syntax Error:
Failed to compile.
./app/javascript/RoastsJson/index.jsx Module build failed: SyntaxError: Unexpected token (6:10)
4 | class RoastsJson extends React.Component {
5 |
> 6 | render: function() {
| ^
7 | roasts = this.props.roasts.map( function(roast) {
8 | return (
9 | <tr key={roast.id}> # ./app/javascript/packs/roastsjson.jsx 3:0-36 # multi (webpack)-dev-server/client?http://localhost:3035 ./app/javascript/packs/roastsjson.jsx
just can't see why this would fail...my unrelated but I was having the same error when playing with files with const = url with the error pointing to the u in url.
app/javascript/RoatsJson/index.jsx
import React from 'react'
import ReactDom from 'react-dom'
class RoastsJson extends React.Component {
render: function() {
roasts = this.props.roasts.map( function(roast) {
return (
<tr key={roast.id}>
<td>{roast.name}</td>
</tr>
);
console.log(roast);
});
return (
<div>
<h1>Roasts</h1>
<div id="roasts">
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{roasts}
</tbody>
</table>
</div>
</div>
);
}
};
export default RoastsJson
I understand I could(should) remove function, such as:
render() {
roasts = this.props.roasts.map( function(roast)
but this then throws the following in the conosle:
unreachable code after return statement
Context
I'm trying to pull a Rails 5.1 resource called roasts into this react component. in my roasts_controller.rb:
def index
#roasts = Roast.all
render component: 'roatsjson', props: {roasts: #roasts}
end
The issue I could see is roasts which is not defined in your component. The better way to render array off elements is to use an array and push elements to it and print that.
Also start using arrow functions to avoid scope issues and function bindings.
Check below updated code and it should work.
class RoastsJson extends React.Component {
render() {
let roasts = []
this.props.roasts.map((roast) => {
roasts.push(
<tr key={roast.id}>
<td>{roast.name}</td>
</tr>
)
});
return (
<div>
<h1>Roasts</h1>
<div id="roasts">
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{roasts}
</tbody>
</table>
</div>
</div>
);
}
};
export default RoastsJson
It seems you pass an unexpected roasts props. You should try to use some checks on your props, using for example the prop-types library.
Also, it's generally a good practice to use pure fonction for a stateless component (see reactpatterns.com). Here one, un-tested :
import React from 'react';
import PropTypes from 'prop-types';
const RoastsJson = ({ roasts }) => (roasts && (<div>
<h1>Roasts</h1>
<div id="roasts">
<table>
<thead>
<th>Name</th>
</thead>
<tbody>
{
roasts.map(({id, name}) => <tr key={id}>
<td>{name}</td>
</tr>)
}
</tbody>
</table>
</div>
</div>));
RoastsJson.propTypes = {
roasts: PropTypes.array
};
export default RoastsJson;

Resources