MEANStack Angular Table - Sorting and Pagination F

2019-09-25 12:51发布

问题:

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 &nbsp; </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 &nbsp; </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

回答1:

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
}