无法设置未定义的 属性 'menuData' (ContextMenu, Angular 8)
Cannot Set Property 'menuData' of undefined (ContextMenu, Angular 8)
我在我的 Angular 8 应用程序中使用上下文菜单,我也在使用 Angular Material。
我正在从内部 API 获取菜单数据,并且我使用该数据创建了一个数据 table。
我想要做的是使用上下文菜单,一旦右键单击该行,就会弹出一个菜单。
现在我可以在调试器中看到数据,但我收到错误消息“无法设置 属性 未定义的菜单数据。
view.component.html
<mat-card>
<mat-card-content>
<div class="view-container mat-elevation-z8">
<table mat-table [dataSource]="fetchedData" matSort matSortActive="{{sortCol}}"
matSortDirection="{{sortDirection}}">
<ng-container [matColumnDef]="column.columnId" *ngFor="let column of viewData.ColumnObjects">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ column.propertyName }}
</th>
<td mat-cell *matCellDef="let action" (contextmenu)="onContextMenu($event, action[column.columnId])">
{{ action[column.columnId] }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="viewData.ColumnIds; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: viewData.ColumnIds"></tr>
</table>
<div style="visibility: hidden; position: fixed" [style.left]="contextMenuPosition.x"
[style.top]="contextMenuPosition.y" [matMenuTriggerFor]="contextMenu">
</div>
<!--Context Right Click Menu-->
<mat-menu #contextMenu="matMenu" #contextMenu2="matMenu">
<ng-template matMenuContent let-action="action">
<button mat-menu-item (click)="onContextMenuAction1(action)">Action 1</button>
<button mat-menu-item (click)="onContextMenuAction2(action)">Action 2</button>
</ng-template>
</mat-menu>
</div>
</mat-card-content>
</mat-card>
view.component.ts
export interface TableRow {
id: number;
col1: string;
col2: string;
}
@Component({
templateUrl: 'view.component.html'
})
export class ViewComponent implements OnInit, OnDestroy, AfterViewInit {
viewData: any;
viewName: string;
viewTag: number;
pageIndex: number;
pageSize: number;
sortCol: string;
sortDirection: string;
filter: ViewFilter;
loadType: string;
fetchedData: any;
dataSource: ViewDataSource;
pageSizeOptions: number[] = [10, 20, 50];
contextMenu: MatMenuTrigger;
contextMenuPosition = { x: '0 px', y: '0 px'};
@ViewChild(MatSort) sort: MatSort;
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatMenuTrigger)
navSub: Subscription;
selection = new SelectionModel<TableRow>(true, []);
primaryTableValue: any;
constructor(private actionService: ActionService, private route: ActivatedRoute, private router: Router, public dialog: MatDialog,) {
// Init these two fields on a dummy ViewData so that the mat-table does not throw errors.
this.viewData = {
ColumnObjects: new Array(),
ViewData: {
DataRows: new Array()
}
};
}
ngOnInit() {
// Init the dataSource
this.dataSource = new ViewDataSource(this.actionService);
// Init the component the first time it is navigated to.
this.initData();
// Subscribe to the router, so that any new navigation to this component loads new data.
this.navSub = this.router.events.subscribe((e: any) => {
if (e instanceof NavigationEnd) {
this.initData();
}
});
// Subscribe to the View in the DataSource (this used to be in initData(), consider moving if unstable -Adam)
this.dataSource.view.subscribe(x => {
if (x.ActionName) {
this.viewName = x.ActionName;
this.viewData = x;
this.fetchedData = this.viewData.TableData;
// Update the paginator and sort.
this.paginator.pageIndex = x.pageIndex;
this.paginator.pageSize = x.pageSize;
this.sort.active = x.sortCol;
this.sort.direction = x.sortDirection;
// Selection updates
// Added this because the checkboxes were duplicating themselves after the merge -Adam
if (this.viewData.ColumnIds.indexOf("checkbox") == -1) {
this.viewData.ColumnIds.unshift("checkbox");
}
this.primaryTableValue = (this.viewData.ViewData.DbrAction.PrimaryTable);
// Filter update
this.filter = x.filter;
}
});
}
// Right Click Context Menu for View Data Table
onContextMenu(event: MouseEvent, action: ViewDataSource) {
event.preventDefault();
debugger;
this.contextMenuPosition.x = event.clientX + 'px';
this.contextMenuPosition.y = event.clientY + 'px';
this.contextMenu.menuData = { 'action': action };
this.contextMenu.menu.focusFirstItem('mouse');
this.contextMenu.openMenu();
}
onContextMenuAction1(action: ViewDataSource) {
alert(`Click on Action 1 for ${action}`);
}
onContextMenuAction2(action: ViewDataSource) {
alert(`Click on Action 2 for ${action}`);
}
initData() {
// Get View details from route URL params.
this.viewTag = +this.route.snapshot.paramMap.get("tag");
this.pageIndex = +this.route.snapshot.paramMap.get("index");
this.pageSize = +this.route.snapshot.paramMap.get("size");
this.sortCol = this.route.snapshot.paramMap.get("col");
this.sortDirection = this.route.snapshot.paramMap.get("dir") == "default" ? "" : this.route.snapshot.paramMap.get("dir");
this.loadType = this.route.snapshot.paramMap.get("lt");
let routeSnapParams = this.route.snapshot.queryParams;
// Get ViewFilter details from the query params
this.filter = (routeSnapParams['ColumnId']) ? new ViewFilter(routeSnapParams['ColumnId'], routeSnapParams['Selection'],
routeSnapParams['Comparator1'], routeSnapParams['UserIn1'], routeSnapParams['AndOrIn'],
routeSnapParams['Comparator2'], routeSnapParams['UserIn2']) : null;
// Load the View from the DataSource
this.dataSource.loadView(this.viewTag, this.pageIndex, this.pageSize, this.sortCol, this.sortDirection, this.filter, this.loadType);
}
ngAfterViewInit() {
// After sorting, jump back to first page of newly sorted data.
this.sort.sortChange.subscribe(
() => {
this.paginator.pageIndex = 0
});
// Sort changes and pagination events should reload the page.
merge(this.sort.sortChange, this.paginator.page)
.pipe(
tap(() => this.loadPage())
).subscribe();
}
// Loads the requested page of the View from the datasource.
loadPage() {
this.dataSource.loadView(
this.viewTag,
this.paginator.pageIndex,
this.paginator.pageSize,
this.sort.active,
this.sort.direction,
this.filter,
"ps");
}
// Opens a Dialog for applying a filter based on a given column.
filterColumn(column: any) {
// Log the unique values of the column.
console.log(column.propertyName + "(" + column.columnId + ") " + "\n Values in Column:");
let contents: string = "";
for (let i in this.viewData.ColumnSets[column.columnId]) {
contents += this.viewData.ColumnSets[column.columnId][i];
contents += ", "
}
console.log(contents);
// Open the ViewFilterDialog
const dialogRef = this.dialog.open(ViewFilterDialogComponent, {
data: {
// Unique Values of the selected column
columnContents: this.viewData.ColumnSets[column.columnId],
// The user-selected filters.
filter: new ViewFilter(column.columnId, null, null, null, null, null, null)
},
width: '450px'
});
// After close, return the user-specified filter.
dialogRef.afterClosed().subscribe((filterInputs: ViewFilter) => {
this.filter = filterInputs;
this.loadPage();
});
}
modifyView() {
const dialogRef = this.dialog.open(ModifyViewComponent, {
//panelClass: 'view-settings'
height: '85%',
width: '60%'
});
}
ngOnDestroy() {
// Unsubscribes from navSub, saving memory and prevent unwanted NavigationEnd events from firing.
if (this.navSub) {
this.navSub.unsubscribe();
}
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.view['source']['value'].TableData;
return numSelected === numRows.length;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected()
? this.selection.clear()
: this.dataSource.view['source']['value'].TableData.forEach((row: TableRow) =>
this.selection.select(row)
);
}
// Delete row functionality
deleteRow() {
const selectedIds = this.selection.selected.map(s => s[0]);
/*
this.selection.selected.forEach(item => {
const index: number = this.dataSource.view['source']['value'].TableData.filter (
(d: TableRow) => d === item
);
this.dataSource.view['source']['value'].TableData.splice(index, 1);
console.log(this.dataSource);
this.dataSource = new ViewDataSource(this.dataSource.view['source']['value'].TableData);
});
this.selection = new SelectionModel<TableRow>(true, []);
*/
this.actionService.deleteRow(selectedIds, this.primaryTableValue).subscribe(response => {
console.log("Success!");
});
}
}
如下声明您的 MatMenuTrigger。
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
或
@ViewChild('contextMenu') contextMenu: MatMenuTrigger;
我在我的 Angular 8 应用程序中使用上下文菜单,我也在使用 Angular Material。
我正在从内部 API 获取菜单数据,并且我使用该数据创建了一个数据 table。 我想要做的是使用上下文菜单,一旦右键单击该行,就会弹出一个菜单。 现在我可以在调试器中看到数据,但我收到错误消息“无法设置 属性 未定义的菜单数据。
view.component.html
<mat-card>
<mat-card-content>
<div class="view-container mat-elevation-z8">
<table mat-table [dataSource]="fetchedData" matSort matSortActive="{{sortCol}}"
matSortDirection="{{sortDirection}}">
<ng-container [matColumnDef]="column.columnId" *ngFor="let column of viewData.ColumnObjects">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ column.propertyName }}
</th>
<td mat-cell *matCellDef="let action" (contextmenu)="onContextMenu($event, action[column.columnId])">
{{ action[column.columnId] }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="viewData.ColumnIds; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: viewData.ColumnIds"></tr>
</table>
<div style="visibility: hidden; position: fixed" [style.left]="contextMenuPosition.x"
[style.top]="contextMenuPosition.y" [matMenuTriggerFor]="contextMenu">
</div>
<!--Context Right Click Menu-->
<mat-menu #contextMenu="matMenu" #contextMenu2="matMenu">
<ng-template matMenuContent let-action="action">
<button mat-menu-item (click)="onContextMenuAction1(action)">Action 1</button>
<button mat-menu-item (click)="onContextMenuAction2(action)">Action 2</button>
</ng-template>
</mat-menu>
</div>
</mat-card-content>
</mat-card>
view.component.ts
export interface TableRow {
id: number;
col1: string;
col2: string;
}
@Component({
templateUrl: 'view.component.html'
})
export class ViewComponent implements OnInit, OnDestroy, AfterViewInit {
viewData: any;
viewName: string;
viewTag: number;
pageIndex: number;
pageSize: number;
sortCol: string;
sortDirection: string;
filter: ViewFilter;
loadType: string;
fetchedData: any;
dataSource: ViewDataSource;
pageSizeOptions: number[] = [10, 20, 50];
contextMenu: MatMenuTrigger;
contextMenuPosition = { x: '0 px', y: '0 px'};
@ViewChild(MatSort) sort: MatSort;
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatMenuTrigger)
navSub: Subscription;
selection = new SelectionModel<TableRow>(true, []);
primaryTableValue: any;
constructor(private actionService: ActionService, private route: ActivatedRoute, private router: Router, public dialog: MatDialog,) {
// Init these two fields on a dummy ViewData so that the mat-table does not throw errors.
this.viewData = {
ColumnObjects: new Array(),
ViewData: {
DataRows: new Array()
}
};
}
ngOnInit() {
// Init the dataSource
this.dataSource = new ViewDataSource(this.actionService);
// Init the component the first time it is navigated to.
this.initData();
// Subscribe to the router, so that any new navigation to this component loads new data.
this.navSub = this.router.events.subscribe((e: any) => {
if (e instanceof NavigationEnd) {
this.initData();
}
});
// Subscribe to the View in the DataSource (this used to be in initData(), consider moving if unstable -Adam)
this.dataSource.view.subscribe(x => {
if (x.ActionName) {
this.viewName = x.ActionName;
this.viewData = x;
this.fetchedData = this.viewData.TableData;
// Update the paginator and sort.
this.paginator.pageIndex = x.pageIndex;
this.paginator.pageSize = x.pageSize;
this.sort.active = x.sortCol;
this.sort.direction = x.sortDirection;
// Selection updates
// Added this because the checkboxes were duplicating themselves after the merge -Adam
if (this.viewData.ColumnIds.indexOf("checkbox") == -1) {
this.viewData.ColumnIds.unshift("checkbox");
}
this.primaryTableValue = (this.viewData.ViewData.DbrAction.PrimaryTable);
// Filter update
this.filter = x.filter;
}
});
}
// Right Click Context Menu for View Data Table
onContextMenu(event: MouseEvent, action: ViewDataSource) {
event.preventDefault();
debugger;
this.contextMenuPosition.x = event.clientX + 'px';
this.contextMenuPosition.y = event.clientY + 'px';
this.contextMenu.menuData = { 'action': action };
this.contextMenu.menu.focusFirstItem('mouse');
this.contextMenu.openMenu();
}
onContextMenuAction1(action: ViewDataSource) {
alert(`Click on Action 1 for ${action}`);
}
onContextMenuAction2(action: ViewDataSource) {
alert(`Click on Action 2 for ${action}`);
}
initData() {
// Get View details from route URL params.
this.viewTag = +this.route.snapshot.paramMap.get("tag");
this.pageIndex = +this.route.snapshot.paramMap.get("index");
this.pageSize = +this.route.snapshot.paramMap.get("size");
this.sortCol = this.route.snapshot.paramMap.get("col");
this.sortDirection = this.route.snapshot.paramMap.get("dir") == "default" ? "" : this.route.snapshot.paramMap.get("dir");
this.loadType = this.route.snapshot.paramMap.get("lt");
let routeSnapParams = this.route.snapshot.queryParams;
// Get ViewFilter details from the query params
this.filter = (routeSnapParams['ColumnId']) ? new ViewFilter(routeSnapParams['ColumnId'], routeSnapParams['Selection'],
routeSnapParams['Comparator1'], routeSnapParams['UserIn1'], routeSnapParams['AndOrIn'],
routeSnapParams['Comparator2'], routeSnapParams['UserIn2']) : null;
// Load the View from the DataSource
this.dataSource.loadView(this.viewTag, this.pageIndex, this.pageSize, this.sortCol, this.sortDirection, this.filter, this.loadType);
}
ngAfterViewInit() {
// After sorting, jump back to first page of newly sorted data.
this.sort.sortChange.subscribe(
() => {
this.paginator.pageIndex = 0
});
// Sort changes and pagination events should reload the page.
merge(this.sort.sortChange, this.paginator.page)
.pipe(
tap(() => this.loadPage())
).subscribe();
}
// Loads the requested page of the View from the datasource.
loadPage() {
this.dataSource.loadView(
this.viewTag,
this.paginator.pageIndex,
this.paginator.pageSize,
this.sort.active,
this.sort.direction,
this.filter,
"ps");
}
// Opens a Dialog for applying a filter based on a given column.
filterColumn(column: any) {
// Log the unique values of the column.
console.log(column.propertyName + "(" + column.columnId + ") " + "\n Values in Column:");
let contents: string = "";
for (let i in this.viewData.ColumnSets[column.columnId]) {
contents += this.viewData.ColumnSets[column.columnId][i];
contents += ", "
}
console.log(contents);
// Open the ViewFilterDialog
const dialogRef = this.dialog.open(ViewFilterDialogComponent, {
data: {
// Unique Values of the selected column
columnContents: this.viewData.ColumnSets[column.columnId],
// The user-selected filters.
filter: new ViewFilter(column.columnId, null, null, null, null, null, null)
},
width: '450px'
});
// After close, return the user-specified filter.
dialogRef.afterClosed().subscribe((filterInputs: ViewFilter) => {
this.filter = filterInputs;
this.loadPage();
});
}
modifyView() {
const dialogRef = this.dialog.open(ModifyViewComponent, {
//panelClass: 'view-settings'
height: '85%',
width: '60%'
});
}
ngOnDestroy() {
// Unsubscribes from navSub, saving memory and prevent unwanted NavigationEnd events from firing.
if (this.navSub) {
this.navSub.unsubscribe();
}
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.view['source']['value'].TableData;
return numSelected === numRows.length;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected()
? this.selection.clear()
: this.dataSource.view['source']['value'].TableData.forEach((row: TableRow) =>
this.selection.select(row)
);
}
// Delete row functionality
deleteRow() {
const selectedIds = this.selection.selected.map(s => s[0]);
/*
this.selection.selected.forEach(item => {
const index: number = this.dataSource.view['source']['value'].TableData.filter (
(d: TableRow) => d === item
);
this.dataSource.view['source']['value'].TableData.splice(index, 1);
console.log(this.dataSource);
this.dataSource = new ViewDataSource(this.dataSource.view['source']['value'].TableData);
});
this.selection = new SelectionModel<TableRow>(true, []);
*/
this.actionService.deleteRow(selectedIds, this.primaryTableValue).subscribe(response => {
console.log("Success!");
});
}
}
如下声明您的 MatMenuTrigger。
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
或
@ViewChild('contextMenu') contextMenu: MatMenuTrigger;