sorting does not works with Virtual scrolling custom data source - angular8

Angular 8 brings really cool feature to implement virtual scroll. They have provide example here regarding usage of it. Although, its really difficult implement with mat-table.
I have been working on to implement virtual scroll on mat-table and found that, we need to have our own custom data source for that. I have create new data source by implementing DataSource class but what I found is that, I loose the sort and other features due to custom data source.
app.component.html
<cdk-virtual-scroll-viewport class="example-viewport">
<table class="table" mat-table #table matSort [dataSource]="dataSource" [multiTemplateDataRows]="true">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> id </th>
<td mat-cell *matCellDef="let element"> {{element.id}} </td>
</ng-container>
<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>
<ng-container matColumnDef="age">
<th mat-header-cell *matHeaderCellDef> Age</th>
<td mat-cell *matCellDef="let element"> {{element.age}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<!-- cdkVirtualScrollViewport uses transform: translateY to correct for all elements that are removed.
We will use a plcaholder row in the table instead because the translate causes problems with the sticky header -->
<tr [style.height.px]="placeholderHeight" mat-row *matRowDef="let row; let index = index; columns: []; when: placeholderWhen"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</cdk-virtual-scroll-viewport>
app.component.ts
import { Component, OnInit, ViewChild, AfterViewInit } from "#angular/core";
import {
CdkVirtualScrollViewport,
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY
} from "#angular/cdk/scrolling";
import { CollectionViewer } from "#angular/cdk/collections";
import { MatSort } from "#angular/material";
// import { GridTableDataSource } from './gridTable.datasource'
import { GridTableDataSource } from "./gridTableM.datasource";
const ROW_HEIGHT = 48;
/**
* Virtual Scroll Strategy
*/
export class CustomVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
constructor() {
super(ROW_HEIGHT, 1000, 2000);
}
attach(viewport: CdkVirtualScrollViewport): void {
this.onDataLengthChanged();
}
}
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
providers: [
{ provide: VIRTUAL_SCROLL_STRATEGY, useClass: CustomVirtualScrollStrategy }
]
})
export class AppComponent implements OnInit, AfterViewInit {
placeholderHeight = 0;
displayedColumns: string[] = ["id", "name", "age"];
dataSource: GridTableDataSource<any>;
rows = Array(200)
.fill(0)
.map((x, i) => {
return { name: "name" + i, id: i, age: 27 };
});
itemSize = 48;
#ViewChild(CdkVirtualScrollViewport, { static: true })
viewport: CdkVirtualScrollViewport;
#ViewChild(MatSort, { static: false }) sort: MatSort;
constructor() {}
ngOnInit() {
this.dataSource = new GridTableDataSource(
this.rows,
this.viewport,
this.itemSize
);
this.dataSource.offsetChange.subscribe(offset => {
this.placeholderHeight = offset;
});
this.dataSource.data = this.rows;
// debugger;
// this.dataSource.sort = this.sort; // this.sort is null here
}
ngAfterViewInit() {
debugger;
this.dataSource.sort = this.sort; // this.sort is null here as well
}
placeholderWhen(index: number, _: any) {
return index == 0;
}
}
To implement sorting and other feature, I just copied code from MatTableDataSource. but somehow, this.sort is always null in any method. I couldn't find reason for this. Can anyone explain this behavior?
Here is stackblitz to play with.

it doesn't work.
angular team doesn't support this
https://github.com/angular/components/issues/19003

Related

Vue Sorted Table (Sorted from highest to lowest)

Hi i am a newbie in vuejs. I wanted to sort a table by Highest to lowest. However i install the library of vue-sorted-table. But when im trying run the code the data always return lowest to highest. Can anyone help me? Thank you..
Here is the code:
<template>
<div id="app">
<sorted-table :values="values" #sort-table="onSort">
<thead>
<tr>
<th scope="col" style="text-align: left; width: 10rem;">
<sort-link name="id">ID</sort-link>
</th>
</tr>
</thead>
<template #body="sort">
<tbody>
<tr v-for="value in sort.values" :key="value.id">
<td>{{ value.id }}</td>
</tr>
</tbody>
</template>
</sorted-table>
</div>
</template>
<script>
import { ChevronUpIcon } from "vue-feather-icons";
export default {
name: "App",
data: function() {
return {
values: [{ id: 2 }, { id: 1 }, { id: 3 }],
sortBy: "",
sortDir: ""
};
},
components: {
ChevronUpIcon
},
methods: {
onSort(sortBy, sortDir) {
this.sortBy = sortBy;
this.sortDir = sortDir;
}
}
};
</script>
<style>
.feather {
transition: 0.3s transform ease-in-out;
}
.feather.asc {
transform: rotate(-180deg);
}
</style>
code can access here:
https://codesandbox.io/s/pedantic-kirch-bx9zv
Add dir property to sorted-table, and make its value equals to desc
<template>
<div id="app">
<sorted-table :values="values" #sort-table="onSort" dir="desc">
<thead>
<tr>
<th scope="col" style="text-align: left; width: 10rem;">
<sort-link name="id">ID</sort-link>
</th>
</tr>
</thead>
<template #body="sort">
<tbody>
<tr v-for="value in sort.values" :key="value.id">
<td>{{ value.id }}</td>
</tr>
</tbody>
</template>
</sorted-table>
</div>
</template>
<script>
import { ChevronUpIcon } from "vue-feather-icons";
export default {
name: "App",
data: function() {
return {
values: [{ id: 2 }, { id: 1 }, { id: 3 }],
sortBy: "",
sortDir: ""
};
},
components: {
ChevronUpIcon
},
methods: {
onSort(sortBy, sortDir) {
this.sortBy = sortBy;
this.sortDir = sortDir;
}
}
};
</script>
<style>
.feather {
transition: 0.3s transform ease-in-out;
}
.feather.asc {
transform: rotate(-180deg);
}
</style>
check https://github.com/BernhardtD/vue-sorted-table and pay attention to dir property of the table, you'll find the answer

:class not updating when clicked in vuejs v-for

I want to add a css class to a item in v-for when the td in clicked
<template>
<div>
<h1>Forces ()</h1>
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-if="loading">
<p>loading...</p>
</section>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th
#click="orderByName = !orderByName">Forces</th>
</tr>
<th>Delete</th>
</thead>
<tbody>
<tr v-for="(force, index) in forces" #click="completeTask(force)" :class="{completed: force.done}" v-bind:key="index">
<td>
<router-link :to="{ name: 'ListForce', params: { name: force.id } }">Link</router-link>
</td>
<th>{{ force.done }}</th>
<th>{{ force.name }}</th>
<th><button v-on:click="removeElement(index)">remove</button></th>
</tr>
</tbody>
</table>
<div>
</div>
</div>
</template>
<script>
import {APIService} from '../APIService';
const apiService = new APIService();
const _ = require('lodash');
export default {
name: 'ListForces',
components: {
},
data() {
return {
question: '',
forces: [{
done: null,
id: null,
name: null
}],
errored: false,
loading: true,
orderByName: false,
};
},
methods: {
getForces(){
apiService.getForces().then((data, error) => {
this.forces = data;
this.forces.map(function(e){
e.done = false;
});
this.loading= false;
console.log(this.forces)
});
},
completeTask(force) {
force.done = ! force.done;
console.log(force.done);
},
removeElement: function (index) {
this.forces.splice(index, 1);
}
},
computed: {
/* forcesOrdered() {
return this.orderByName ? _.orderBy(this.forces, 'name', 'desc') : this.forces;
}*/
},
mounted() {
this.getForces();
},
}
</script>
<style>
.completed {
text-decoration: line-through;
}
</style>
I want a line to go through the items when clicked, but the dom doesn't update. If I go into the vue tab in chrome I can see the force.done changes for each item but only if I click out of the object and click back into it. I'm not to sure why the state isn't updating as it's done so when I have used an click and a css bind before.
I've tried to make minimal changes to get this working:
// import {APIService} from '../APIService';
// const apiService = new APIService();
// const _ = require('lodash');
const apiService = {
getForces () {
return Promise.resolve([
{ id: 1, name: 'Red' },
{ id: 2, name: 'Green' },
{ id: 3, name: 'Blue' }
])
}
}
new Vue({
el: '#app',
name: 'ListForces',
components: {
},
data() {
return {
question: '',
forces: [{
done: null,
id: null,
name: null
}],
errored: false,
loading: true,
orderByName: false,
};
},
methods: {
getForces(){
apiService.getForces().then((data, error) => {
for (const force of data) {
force.done = false;
}
this.forces = data;
this.loading= false;
console.log(this.forces)
});
},
completeTask(force) {
force.done = ! force.done;
console.log(force.done);
},
removeElement: function (index) {
this.forces.splice(index, 1);
}
},
mounted() {
this.getForces();
}
})
.completed {
text-decoration: line-through;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<div>
<h1>Forces ()</h1>
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-if="loading">
<p>loading...</p>
</section>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th
#click="orderByName = !orderByName">Forces</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="(force, index) in forces" #click="completeTask(force)" :class="{completed: force.done}" v-bind:key="index">
<th>{{ force.done }}</th>
<th>{{ force.name }}</th>
<th><button v-on:click="removeElement(index)">remove</button></th>
</tr>
</tbody>
</table>
<div>
</div>
</div>
</div>
The key problem was here:
this.forces = data;
this.forces.map(function(e){
e.done = false;
});
The problem here is that the property done is being added to the data after it has been made reactive. The reactivity observers get added as soon as the line this.forces = data runs. Adding done after that counts as adding a new property, which won't work.
It's also a misuse of map so I've switched it to a for/of loop instead.
for (const force of data) {
force.done = false;
}
this.forces = data; // <- data becomes reactive here, including 'done'
On an unrelated note I've tweaked the template to move the Delete header inside the top row.
Make sure force.done is set to false in data before initialization so it is reactive.
data() {
return {
force:{
done: false;
}
}
}
If force exists but has no done member set, you can use Vue.set instead of = to create a reactive value after initialization.
Vue.set(this.force, 'done', true);

MEANStack Angular Table - Sorting and Pagination Functionalities

I'm developing a WebApp with MEANStack, currently working on the Front End with Angular. My App simply reads SQL Data Tables and displays them on a browser in the form of Angular Tables.
Here's how it looks like:
Data Table Display MEANStack App
Unfortunately the Sort functionality does not work. Any idea why? Furthermore, how can I implement the "scroll left and right" functionality, given that my tables are a bit large?
Here's my code (Please do not hesitate to ask for more code):
tables-list.component.html
<mat-spinner *ngIf="isLoading"></mat-spinner>
<h1 class="mat-body-2">Process List </h1>
<mat-accordion multi="true" *ngIf="userIsAuthenticated && !isLoading">
<mat-expansion-panel>
<mat-expansion-panel-header>
Process List
</mat-expansion-panel-header>
<table mat-table [dataSource]="processTables" matSort class="mat-elevation-z8" *ngIf="userIsAuthenticated">
<!-- ProcessID Column -->
<ng-container matColumnDef="ProcessID">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ProcessID </th>
<td mat-cell *matCellDef="let element"> {{element.ProcessID}} </td>
</ng-container>
<!-- ProcessName Column -->
<ng-container matColumnDef="ProcessName">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ProcessName </th>
<td mat-cell *matCellDef="let element"> {{element.ProcessName}} </td>
</ng-container>
<!-- PackageVersion Column -->
<ng-container matColumnDef="PackageVersion">
<th mat-header-cell *matHeaderCellDef mat-sort-header> PackageVersion </th>
<td mat-cell *matCellDef="let element"> {{element.PackageVersion}} </td>
</ng-container>
<!-- RobotType Column -->
<ng-container matColumnDef="RobotType">
<th mat-header-cell *matHeaderCellDef mat-sort-header> RobotType </th>
<td mat-cell *matCellDef="let element"> {{element.RobotType}} </td>
</ng-container>
<!-- PackagePath Column -->
<ng-container matColumnDef="PackagePath">
<th mat-header-cell *matHeaderCellDef mat-sort-header> PackagePath </th>
<td mat-cell *matCellDef="let element"> {{element.PackagePath}} </td>
</ng-container>
<!-- CreationTime Column -->
<ng-container matColumnDef="CreationTime">
<th mat-header-cell *matHeaderCellDef mat-sort-header> CreationTime </th>
<td mat-cell *matCellDef="let element"> {{element.CreationTime}} </td>
</ng-container>
<!-- Status Column -->
<ng-container matColumnDef="Status">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Status </th>
<td mat-cell *matCellDef="let element"> {{element.Status}} </td>
</ng-container>
<!-- Header and Row Declarations -->
<tr mat-header-row *matHeaderRowDef="displayedprocessTablesColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedprocessTablesColumns;"></tr>
</table>
</mat-expansion-panel>
</mat-accordion>
<br> <h1 class="mat-body-2">Applications List </h1>
<mat-accordion multi="true" *ngIf="userIsAuthenticated && !isLoading">
<mat-expansion-panel>
<mat-expansion-panel-header>
Applications List
</mat-expansion-panel-header>
<table mat-table [dataSource]="applicationsTables" matSort class="mat-elevation-z8" *ngIf="userIsAuthenticated">
<!-- ProcessName Column -->
<ng-container matColumnDef="ProcessName">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ProcessName </th>
<td mat-cell *matCellDef="let element"> {{element.ProcessName}} </td>
</ng-container>
<!-- PackageVersion Column -->
<ng-container matColumnDef="PackageVersion">
<th mat-header-cell *matHeaderCellDef mat-sort-header> PackageVersion </th>
<td mat-cell *matCellDef="let element"> {{element.PackageVersion}} </td>
</ng-container>
<!-- WorkflowsBelongingToProcess Column -->
<ng-container matColumnDef="WorkflowsBelongingToProcess">
<th mat-header-cell *matHeaderCellDef mat-sort-header> WorkflowsBelongingToProcess </th>
<td mat-cell *matCellDef="let element"> {{element.WorkflowsBelongingToProcess}} </td>
</ng-container>
<!-- ApplicationsBelongingToWorkflow Column -->
<ng-container matColumnDef="ApplicationsBelongingToWorkflow">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ApplicationsBelongingToWorkflow </th>
<td mat-cell *matCellDef="let element"> {{element.ApplicationsBelongingToWorkflow}} </td>
</ng-container>
<!-- Header and Row Declarations -->
<tr mat-header-row *matHeaderRowDef="displayedApplicationsTablesColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedApplicationsTablesColumns;"></tr>
</table>
</mat-expansion-panel>
</mat-accordion>
tables.module.ts
import { NgModule } from "#angular/core";
import { ReactiveFormsModule } from "#angular/forms";
import { CommonModule } from "#angular/common";
import { RouterModule } from "#angular/router";
import { TableListComponent } from "./tables-list.component";
import { AngularMaterialModule } from "../angular-material.module";
#NgModule({
declarations: [TableListComponent],
imports: [
CommonModule,
ReactiveFormsModule,
AngularMaterialModule,
RouterModule
]
})
export class TablesModule {}
angular-material.module.ts
import { NgModule } from "#angular/core";
import {
MatInputModule,
MatCardModule,
MatButtonModule,
MatToolbarModule,
MatExpansionModule,
MatProgressSpinnerModule,
MatPaginatorModule,
MatDialogModule,
MatSelectModule,
MatListModule,
MatIconModule,
MatTableModule,
MatSortModule,
MatTabsModule,
MatAutocompleteModule,
MatBadgeModule,
MatBottomSheetModule,
MatButtonToggleModule,
MatCheckboxModule,
MatChipsModule,
MatStepperModule,
MatDatepickerModule,
MatGridListModule,
MatDividerModule,
MatMenuModule,
MatNativeDateModule,
MatProgressBarModule,
MatRadioModule,
MatRippleModule,
} from "#angular/material";
import { CdkTableModule } from "#angular/cdk/table";
import { CdkTreeModule } from "#angular/cdk/tree";
#NgModule({
exports: [
MatInputModule,
MatCardModule,
MatButtonModule,
MatToolbarModule,
MatExpansionModule,
MatProgressSpinnerModule,
MatPaginatorModule,
MatDialogModule,
MatSelectModule,
MatListModule,
MatIconModule,
MatTableModule,
MatSortModule,
MatTabsModule,
CdkTableModule,
CdkTreeModule,
MatAutocompleteModule,
MatBadgeModule,
MatBottomSheetModule,
MatButtonToggleModule,
MatCheckboxModule,
MatChipsModule,
MatStepperModule,
MatDatepickerModule,
MatDividerModule,
MatGridListModule,
MatMenuModule,
MatNativeDateModule,
MatProgressBarModule,
MatRadioModule,
MatRippleModule,
]
})
export class AngularMaterialModule {}
tables-list.component.ts:
import { Component, OnInit, OnDestroy, ViewChild } from "#angular/core";
import { ProcessTable, ApplicationsTable } from "./tables.model";
import { PageEvent, MatTableDataSource, MatSort } from "#angular/material";
import { Subscription } from "rxjs";
import { TablesService } from "./tables.service";
import { AuthService } from "../auth/auth.service";
#Component({
// We load the component via routing and therefore we do not need a selector
selector: "app-tables",
templateUrl: "./tables-list.component.html",
styleUrls: ["./tables-list.component.css"]
}) // Turn class into component by adding #Component Decorator
export class TableListComponent implements OnInit, OnDestroy {
processTables: ProcessTable[] = [];
applicationsTables: ApplicationsTable[] = [];
isLoading = false;
totalTables = 0;
tablesPerPage = 5;
currentPage = 1;
pageSizeOptions = [1, 2, 5, 10];
dataSource = new MatTableDataSource(this.processTables);
#ViewChild(MatSort) sort: MatSort;
displayedprocessTablesColumns: string[] = [
"ProcessID",
"ProcessName",
"PackageVersion",
"RobotType",
"PackagePath",
"CreationTime",
"Status"
];
displayedApplicationsTablesColumns: string[] = [
"ProcessName",
"PackageVersion",
"WorkflowsBelongingToProcess",
"ApplicationsBelongingToWorkflow"
];
userIsAuthenticated = false;
userId: string;
isAdmin: boolean;
private tablesSub: Subscription;
private authStatusSub: Subscription;
constructor(
public tablesService: TablesService,
private authService: AuthService
) {}
ngOnInit() {
this.isLoading = true;
this.dataSource.sort = this.sort;
this.tablesService.getProcessTables(this.tablesPerPage, this.currentPage);
this.userId = this.authService.getUserId();
this.tablesSub = this.tablesService
.getTableUpdateListener()
.subscribe((tableData: { processTables: ProcessTable[]; applicationsTables: ApplicationsTable[]; tableCount: number }) => {
this.isLoading = false;
this.totalTables = tableData.tableCount;
this.processTables = tableData.processTables;
this.applicationsTables = tableData.applicationsTables;
});
this.userIsAuthenticated = this.authService.getIsAuth();
// console.log("Is authenticated: " + this.userIsAuthenticated);
this.authStatusSub = this.authService
.getAuthStatusListener()
.subscribe(isAuthenticated => {
this.userIsAuthenticated = isAuthenticated;
});
}
onLogout() {
this.authService.logout();
}
ngOnDestroy() {
this.tablesSub.unsubscribe();
this.authStatusSub.unsubscribe();
}
}
Tables.service.ts:
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
import { Subject } from "rxjs";
import { map } from "rxjs/operators";
import { Router } from "#angular/router";
import { environment } from "../../environments/environment";
import { ProcessTable, ApplicationsTable } from "./tables.model";
const BACKEND_URL = environment.apiUrl + "/tables/";
#Injectable({ providedIn: "root" })
export class TablesService {
private processTables: ProcessTable[] = [];
private applicationsTables: ApplicationsTable[] = [];
private tablesUpdated = new Subject<{ processTables: ProcessTable[]; applicationsTables: ApplicationsTable[]; tableCount: number }>();
constructor(private http: HttpClient, private router: Router) {}
getProcessTables(tablesPerPage: number, currentPage: number) {
const queryParams = `?pagesize=${tablesPerPage}&page=${currentPage}`;
this.http
.get<{ processTables: ProcessTable[]; applicationsTables: ApplicationsTable[]; maxTables: number }>(
BACKEND_URL + queryParams
)
.pipe(
map((tableData: { processTables: ProcessTable[]; applicationsTables: ApplicationsTable[]; maxTables: number }) => {
return {
processTables: tableData.processTables.processTables.map(table => {
return {
ProcessID: table.ProcessID
ProcessName: table.ProcessName,
PackageVersion: table.PackageVersion,
RobotType: table.RobotType,
PackagePath: table.PackagePath,
CreationTime: table.CreationTime,
Status: table.Status
};
}),
applicationsTables: tableData.applicationsTables.applicationsTables.map(table => {
return {
ProcessName: table.ProcessName,
PackageVersion: table.PackageVersion,
WorkflowsBelongingToProcess: table.WorkflowsBelongingToProcess,
ApplicationsBelongingToWorkflow: table.ApplicationsBelongingToWorkflow
};
}),
maxTables: tableData.maxTables
};
})
)
.subscribe(transformedTablesData => {
this.processTables = transformedTablesData.processTables;
this.applicationsTables = transformedTablesData.applicationsTables;
this.tablesUpdated.next({
processTables: [...this.processTables],
applicationsTables: [...this.applicationsTables],
tableCount: transformedTablesData.maxTables
});
});
}
getTableUpdateListener() {
return this.tablesUpdated.asObservable();
}
}
Tables\model.ts:
export interface Table {
ProcessID: string;
ProcessName: string;
PackageVersion: string;
RobotType: string;
PackagePath: string;
CreationTime: string;
Status: string;
}
export interface ApplicationsTable {
ProcessName: string;
PackageVersion: string;
WorkflowsBelongingToProcess: string;
ApplicationsBelongingToWorkflow: string;
}
Backend\controllers\tables.js:
const sequelize = require("../sequelize");
const getProcessTables = (req, res) => {
return sequelize
.query("SELECT * FROM dbo.Process", { type: sequelize.QueryTypes.SELECT })
.then(fetchedtables => {
return {
message: "Process table fetched from the server",
processTables: fetchedtables,
maxProcessTables: fetchedtables.length
};
});
};
const getApplicationsTables = (req, res) => {
return sequelize
.query("SELECT * FROM dbo.Applications", {
type: sequelize.QueryTypes.SELECT
})
.then(fetchedtables => {
return {
message: "Applications Table fetched from the server",
applicationsTables: fetchedtables,
maxApplicationsTables: fetchedtables.length
};
});
};
exports.getTables = (req, res) => {
return Promise.all([
getApplicationsTables(req, res),
getProcessTables(req, res)
]).then(tables => {
res.status(200).json({
applicationsTables: tables[0],
processTables: tables[1]
});
});
};
Backend\routes\tables.js:
const express = require("express");
const TableController = require("../controllers/tables")
const router = express.Router({ mergeParams: true });
router.get("", TableController.getTables);
module.exports = router;
How can I fix it?
Many Thanks
Gennaro
I believe you are using Angular Material 7.x.x. Can we assign datasource to MatTableSource()? It has more rich properties to encapsulate the sorting, pagination,...
Now that you are using any instance of processTables object. Sorting or pagination cannot be linked. Please use this example https://stackblitz.com/angular/pakljarjylj?file=app%2Ftable-sorting-example.ts
tables-list.component.ts:
import { Component, OnInit, OnDestroy } from "#angular/core";
import { ProcessTable, ApplicationsTable } from "./tables.model";
import { PageEvent } from "#angular/material";
import { Subscription } from "rxjs";
import { TablesService } from "./tables.service";
import { AuthService } from "../auth/auth.service";
#Component({
// We load the component via routing and therefore we do not need a selector
selector: "app-tables",
templateUrl: "./tables-list.component.html",
styleUrls: ["./tables-list.component.css"]
}) // Turn class into component by adding #Component Decorator
export class TableListComponent implements OnInit, OnDestroy {
// ....other lines
dataSource = new MatTableDataSource(processTables);
// ....other lines
#ViewChild(MatSort) sort: MatSort;
// ....other lines
ngOnInit() {
this.dataSource.sort = this.sort;
}
// ....other lines
}

MEAN-Stack - Cannot read property 'map' of undefined

I'm developing a WebApp with MEANStack, using Sequelize to access SQL Databases. Unfortunately I get the following error on the client's side: core.js:1673 ERROR TypeError: Cannot read property 'map' of undefined
at MapSubscriber.project (tables.service.ts:39)
"Line 39" of the error is applicationsTables: tableData.applicationsTables.map(table => {
Here's how the DT on the server side looks like:
Data Table on the Browser - Server Side
And here's how the error on the client's side looks like:
Error Messages on the Chrome developers' tools view
Here's my code
tables-list.component.html
<mat-spinner *ngIf="isLoading"></mat-spinner>
<h1 class="mat-body-2">Process List </h1>
<mat-accordion multi="true" *ngIf="userIsAuthenticated && !isLoading">
<mat-expansion-panel>
<mat-expansion-panel-header>
Process List
</mat-expansion-panel-header>
<table mat-table [dataSource]="processTables" matSort class="mat-elevation-z8" *ngIf="userIsAuthenticated">
<!-- ProcessName Column -->
<ng-container matColumnDef="ProcessName">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ProcessName </th>
<td mat-cell *matCellDef="let element"> {{element.ProcessName}} </td>
</ng-container>
<!-- PackageVersion Column -->
<ng-container matColumnDef="PackageVersion">
<th mat-header-cell *matHeaderCellDef mat-sort-header> PackageVersion </th>
<td mat-cell *matCellDef="let element"> {{element.PackageVersion}} </td>
</ng-container>
<!-- RobotType Column -->
<ng-container matColumnDef="RobotType">
<th mat-header-cell *matHeaderCellDef mat-sort-header> RobotType </th>
<td mat-cell *matCellDef="let element"> {{element.RobotType}} </td>
</ng-container>
<!-- PackagePath Column -->
<ng-container matColumnDef="PackagePath">
<th mat-header-cell *matHeaderCellDef mat-sort-header> PackagePath </th>
<td mat-cell *matCellDef="let element"> {{element.PackagePath}} </td>
</ng-container>
<!-- CreationTime Column -->
<ng-container matColumnDef="CreationTime">
<th mat-header-cell *matHeaderCellDef mat-sort-header> CreationTime </th>
<td mat-cell *matCellDef="let element"> {{element.CreationTime}} </td>
</ng-container>
<!-- Status Column -->
<ng-container matColumnDef="Status">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Status </th>
<td mat-cell *matCellDef="let element"> {{element.Status}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedprocessTablesColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedprocessTablesColumns;"></tr>
</table>
</mat-expansion-panel>
</mat-accordion>
<br> <h1 class="mat-body-2">Applications List </h1>
tables-list.component.ts:
import { Component, OnInit, OnDestroy } from "#angular/core";
import { Table, ApplicationsTable } from "./tables.model";
import { PageEvent } from "#angular/material";
import { Subscription } from "rxjs";
import { TablesService } from "./tables.service";
import { AuthService } from "../auth/auth.service";
#Component({
// We load the component via routing and therefore we do not need a selector
selector: "app-tables",
templateUrl: "./tables-list.component.html",
styleUrls: ["./tables-list.component.css"]
}) // Turn class into component by adding #Component Decorator
export class TableListComponent implements OnInit, OnDestroy {
processTables: Table[] = [];
applicationsTables: ApplicationsTable[] = [];
isLoading = false;
totalTables = 0;
tablesPerPage = 5;
currentPage = 1;
pageSizeOptions = [1, 2, 5, 10];
displayedprocessTablesColumns: string[] = ["ProcessName", "PackageVersion", "RobotType", "PackagePath", "CreationTime", "Status" ];
userIsAuthenticated = false;
userId: string;
isAdmin: boolean;
private tablesSub: Subscription;
private authStatusSub: Subscription;
constructor(
public tablesService: TablesService,
private authService: AuthService
) {}
ngOnInit() {
this.isLoading = true;
this.tablesService.getTables(this.tablesPerPage, this.currentPage);
this.userId = this.authService.getUserId();
this.tablesSub = this.tablesService
.getTableUpdateListener()
.subscribe((tableData: { processTables: Table[]; applicationsTables: ApplicationsTable[]; tableCount: number }) => {
this.isLoading = false;
this.totalTables = tableData.tableCount;
this.processTables = tableData.processTables;
this.applicationsTables = tableData.applicationsTables;
console.log(tableData.applicationsTables);
});
this.userIsAuthenticated = this.authService.getIsAuth();
// console.log("Is authenticated: " + this.userIsAuthenticated);
this.authStatusSub = this.authService
.getAuthStatusListener()
.subscribe(isAuthenticated => {
this.userIsAuthenticated = isAuthenticated;
});
}
onChangedPage(pageData: PageEvent) {
this.isLoading = true;
this.currentPage = pageData.pageIndex + 1;
this.tablesPerPage = pageData.pageSize;
this.tablesService.getTables(this.tablesPerPage, this.currentPage);
}
onLogout() {
this.authService.logout();
}
ngOnDestroy() {
this.tablesSub.unsubscribe();
this.authStatusSub.unsubscribe();
}
}
Tables.service.ts:
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
import { Subject } from "rxjs";
import { map } from "rxjs/operators";
import { Router } from "#angular/router";
import { environment } from "../../environments/environment";
import { Table, ApplicationsTable } from "./tables.model";
const BACKEND_URL = environment.apiUrl + "/tables/";
#Injectable({ providedIn: "root" })
export class TablesService {
private processTables: Table[] = [];
private applicationsTables: ApplicationsTable[] = [];
private tablesUpdated = new Subject<{ processTables: Table[]; applicationsTables: ApplicationsTable[]; tableCount: number }>();
constructor(private http: HttpClient, private router: Router) {}
getTables(tablesPerPage: number, currentPage: number) {
const queryParams = `?pagesize=${tablesPerPage}&page=${currentPage}`;
this.http
.get<{ processTables: Table[]; applicationsTables: ApplicationsTable[]; maxTables: number }>(
BACKEND_URL + queryParams
)
.pipe(
map((tableData: { processTables: Table[]; applicationsTables: ApplicationsTable[]; maxTables: number }) => {
return {
processTables: tableData.processTables.map(table => {
return {
ProcessName: table.ProcessName,
PackageVersion: table.PackageVersion,
RobotType: table.RobotType,
PackagePath: table.PackagePath,
CreationTime: table.CreationTime,
Status: table.Status
};
}),
applicationsTables: tableData.applicationsTables.map(table => {
return {
ProcessName: table.ProcessName,
PackageVersion: table.PackageVersion,
WorkflowsBelongingToProcess: table.WorkflowsBelongingToProcess,
ApplicationsBelongingToWorkflow: table.ApplicationsBelongingToWorkflow
};
}),
maxTables: tableData.maxTables
};
})
)
.subscribe(transformedTablesData => {
this.processTables = transformedTablesData.processTables;
this.tablesUpdated.next({
processTables: [...this.processTables],
applicationsTables: [...this.applicationsTables],
tableCount: transformedTablesData.maxTables
});
});
}
getTableUpdateListener() {
return this.tablesUpdated.asObservable();
}
getTable(id: string) {
return this.http.get<{
ProcessName: string;
PackageVersion: string;
RobotType: string;
PackagePath: string;
CreationTime: string;
Status: string;
}>(BACKEND_URL + id);
}
}
Tables\model.ts:
export interface Table {
ProcessName: string;
PackageVersion: string;
RobotType: string;
PackagePath: string;
CreationTime: string;
Status: string;
}
export interface ApplicationsTable {
ProcessName: string;
PackageVersion: string;
WorkflowsBelongingToProcess: string;
ApplicationsBelongingToWorkflow: string;
}
Backend\models\tables.js:
Backend\controllers\tables.js:
const sequelize = require("../sequelize");
exports.getProcessTables = (req, res) => {
sequelize.query("SELECT * FROM dbo.Process", { type: sequelize.QueryTypes.SELECT})
.then(fetchedtables => {
res.status(200).json({
message: "Process table fetched from the server",
processTables: fetchedtables,
maxProcessTables: fetchedtables.length
});
});
};
exports.getApplicationsTables = (req, res) => {
sequelize.query("SELECT * FROM dbo.Applications", { type: sequelize.QueryTypes.SELECT})
.then(fetchedtables => {
res.status(200).json({
message: "Applications Table fetched from the server",
applicationTables: fetchedtables,
maxApplicationsTables: fetchedtables.length
});
});
};
Backend\routes\tables.js:
const express = require("express");
const TableController = require("../controllers/tables")
const router = express.Router({ mergeParams: true });
router.get("", TableController.getProcessTables);
router.get("", TableController.getApplicationsTables);
module.exports = router;
How can I fix it?
Many Thanks
Gennaro
You're returning an object which has a property 'retrievedTables' from the server but on the client you are trying to access 'tables' which doesn't exist.
You can fix this in either Backend\controllers\tables.js or Tables.service.ts. To fix it on the server, just change retrievedTables: tables to tables: tables so the client is getting the fields it expects. If you were to fix it on the client instead you would need to reference retrievedTables rather than tables and update your types accordingly. You're also not sending a maxTables from the server so you will want to add that. Perhaps, maxTables: tables.length.
You also need to make sure you correctly reference the property names. On the server you are sending table properties with an uppercase first letter and on the client you are reading properties with a lowercase first letter which leads to it being undefined.
Be careful with type definitions too. In this case, you are saying that the tables property of the return object of the this.http.get is of type any and then try to call its map method. Only certain types have a map method so be more specific about what you are expecting. Even specifying the type as an array of any types is better as you then guarantee the map method:
this.http
.get<{ tables: any[]; maxTables: number }>(
BACKEND_URL + queryParams
)
That could still be improved further by specifying a specific type rather than any, especially as you go on to use its properties. The better type would be the same one used for the return type of getTable which can be defined as an interface for reuse.
interface TableInstance {
processName: string;
packageVersion: string;
robotType: string;
packagePath: string;
creationTime: string;
status: string;
}
Then, you would define the type returned from get as below.
.get<{ tables: TableInstance[]; maxTables: number }>(
You can set the type of the map function too, which will work instead of doing the above, which directly addresses the problematic line from your error message.
.pipe(
map((tableData: { tables: TableInstance[]; maxTables: number }) => {
return {

PrimeNg TurboTable filters are not working with LazyLoad

I am unable to use filters with lazy load option. Can anyone help me if i am missing anything here?
I read primeng documentation this and this and this with examples. I could find any solution.
When I use both [lazy] and filters together, filters are not working. If I just remove [lazy]="true" from HTML would work the filters. How can I achieve both?
import { Component, OnInit, Input } from '#angular/core';
import { SelectItem, LazyLoadEvent } from 'primeng/primeng';
import { MyService } from '../services/my.service';
import { ColumnHeaders } from '.';
#Component({
selector: 'myList-table',
templateUrl: 'myList-table.component.html',
styleUrls: ['myList-table.component.less']
})
export class myListTableComponent implements OnInit {
rowCount = { 'value': 5 };
totalRecords: number = 0;
pageNavLinks: number = 0;
myList: any = [];
columnHeaders = ColumnHeaders;
#Input() myListStatus = 'live';
constructor(private myService: MyService) { }
ngOnInit() { this.lazyLoad({ 'first': 0 }); }
lazyLoad(event: LazyLoadEvent) {
const pageNumber = Math.round(event.first / this.rowCount.value) + 1;
this.myService.getmyList(this.myListStatus, pageNumber, this.rowCount.value)
.subscribe(response => {
this.myList = response.batches;
this.totalRecords = response.batches[0].TotalRowCount;
this.pageNavLinks = Math.round(this.totalRecords / this.rowCount.value);
});
}
changeCount() {
this.totalRecords = 0;
this.pageNavLinks = 0;
this.lazyLoad({ 'first': 0 });
}
}
<div class="ui-g-12">
<p-table #dt [columns]="columnHeaders" [value]="myList" [paginator]="true" [rows]="rowCount?.value" [totalRecords]="totalRecords" [responsive]="true" (onLazyLoad)="lazyLoad($event)" [lazy]="true">
<ng-template pTemplate="header" let-columns>
<tr>
<th *ngFor="let col of columns">
{{col.header}}
</th>
</tr>
<tr>
<th *ngFor="let col of columns">
<div class="search-ip-table">
<i class="fa fa-search"></i>
<input pInputText type="text" (input)="dt.filter($event.target.value, col.field, col.filterMatchMode)">
</div>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowData let-columns="columns">
<tr [pSelectableRow]="rowData">
<td>{{rowData.id}}</td>
<td>{{rowData.name}}</td>
<td>{{rowData.TName}}</td>
<td>{{rowData.Base}}</td>
<td>{{rowData.Date | date:'medium' }}</td>
</tr>
</ng-template>
<ng-template pTemplate="paginatorleft">
<span>Total Records: {{totalRecords}}</span>
</ng-template>
<ng-template pTemplate="paginatorright">
<p-dropdown [options]="pageRecordsCountOptions" [(ngModel)]="rowCount" optionLabel="value" (onChange)="changeCount()"></p-dropdown>
</ng-template>
</p-table>
</div>
When [lazy]="true", filter doesn't work in the front-end, but leave this work to the back-end by (onLazyLoad) event.
If you want filter your data in the front-end, you can into lazyLoad() method :
.subscribe(response =>. ...
using filter informations provided by event parameter.