MEAN-Stack - 无法读取未定义的 属性 'map'
MEAN-Stack - Cannot read property 'map' of undefined
我正在使用 MEANStack 开发 WebApp,使用 Sequelize 访问 SQL 数据库。不幸的是,我在客户端收到以下错误:core.js:1673 ERROR TypeError: Cannot read 属性 'map' of undefined
在 MapSubscriber.project (tables.service.ts:39)
"Line 39"的错误是applicationsTables: tableData.applicationsTables.map(table => {
服务器端的 DT 如下所示:
Data Table on the Browser - Server Side
客户端的错误如下所示:
Error Messages on the Chrome developers' tools view
这是我的代码
表-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>
表-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;
我该如何解决?
非常感谢
真纳罗
您正在 returning 一个对象,该对象具有来自服务器的 属性 'retrievedTables',但在客户端上您正试图访问 'tables' 而没有存在。
您可以在 Backend\controllers\tables.js
或 Tables.service.ts
中解决此问题。要在服务器上修复它,只需将 retrievedTables: tables
更改为 tables: tables
以便客户端获得它期望的字段。如果您要在客户端上修复它,则需要引用 retrievedTables 而不是 tables 并相应地更新您的类型。您也没有从服务器发送 maxTables,因此您需要添加它。也许,maxTables: tables.length
.
您还需要确保正确引用 属性 名称。在服务器上,您发送 table 首字母大写的属性,而在客户端上,您正在读取首字母小写的属性,这导致它未定义。
也要注意类型定义。在这种情况下,您是在说 this.http.get
的 return 对象的 tables 属性 是 any 类型,然后尝试调用其 map 方法。只有某些类型具有 map 方法,因此请更具体地说明您的期望。即使将类型指定为任何类型的数组也更好,因为您可以保证 map 方法:
this.http
.get<{ tables: any[]; maxTables: number }>(
BACKEND_URL + queryParams
)
这仍然可以通过指定特定类型而不是任何类型来进一步改进,尤其是当您继续使用其属性时。更好的类型与用于 return 类型的 getTable 的类型相同,可以将其定义为可重用的接口。
interface TableInstance {
processName: string;
packageVersion: string;
robotType: string;
packagePath: string;
creationTime: string;
status: string;
}
然后,您将从 get 中定义 return 类型,如下所示。
.get<{ tables: TableInstance[]; maxTables: number }>(
您也可以设置 map 函数的类型,这样就可以代替上面的操作,直接解决错误消息中有问题的行。
.pipe(
map((tableData: { tables: TableInstance[]; maxTables: number }) => {
return {
我正在使用 MEANStack 开发 WebApp,使用 Sequelize 访问 SQL 数据库。不幸的是,我在客户端收到以下错误:core.js:1673 ERROR TypeError: Cannot read 属性 'map' of undefined 在 MapSubscriber.project (tables.service.ts:39)
"Line 39"的错误是applicationsTables: tableData.applicationsTables.map(table => {
服务器端的 DT 如下所示: Data Table on the Browser - Server Side
客户端的错误如下所示: Error Messages on the Chrome developers' tools view
这是我的代码
表-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>
表-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;
我该如何解决? 非常感谢 真纳罗
您正在 returning 一个对象,该对象具有来自服务器的 属性 'retrievedTables',但在客户端上您正试图访问 'tables' 而没有存在。
您可以在 Backend\controllers\tables.js
或 Tables.service.ts
中解决此问题。要在服务器上修复它,只需将 retrievedTables: tables
更改为 tables: tables
以便客户端获得它期望的字段。如果您要在客户端上修复它,则需要引用 retrievedTables 而不是 tables 并相应地更新您的类型。您也没有从服务器发送 maxTables,因此您需要添加它。也许,maxTables: tables.length
.
您还需要确保正确引用 属性 名称。在服务器上,您发送 table 首字母大写的属性,而在客户端上,您正在读取首字母小写的属性,这导致它未定义。
也要注意类型定义。在这种情况下,您是在说 this.http.get
的 return 对象的 tables 属性 是 any 类型,然后尝试调用其 map 方法。只有某些类型具有 map 方法,因此请更具体地说明您的期望。即使将类型指定为任何类型的数组也更好,因为您可以保证 map 方法:
this.http
.get<{ tables: any[]; maxTables: number }>(
BACKEND_URL + queryParams
)
这仍然可以通过指定特定类型而不是任何类型来进一步改进,尤其是当您继续使用其属性时。更好的类型与用于 return 类型的 getTable 的类型相同,可以将其定义为可重用的接口。
interface TableInstance {
processName: string;
packageVersion: string;
robotType: string;
packagePath: string;
creationTime: string;
status: string;
}
然后,您将从 get 中定义 return 类型,如下所示。
.get<{ tables: TableInstance[]; maxTables: number }>(
您也可以设置 map 函数的类型,这样就可以代替上面的操作,直接解决错误消息中有问题的行。
.pipe(
map((tableData: { tables: TableInstance[]; maxTables: number }) => {
return {