无法设置未定义的 属性 '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;