0
votes

I want to have a menu in my app bar that can get its menu items from child components. I am working with the Angular Material "mat-menu" and I'm able to display the menu item but I can't seem to fire off the associated function on the child component.

The app.component.html (parent):

  <div>
    <mat-toolbar style="display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 12px">
      <div>
        <button type="button" mat-icon-button id="btnMore" [matMenuTriggerFor]="appMenu" [matMenuTriggerData]="menuData">
          <mat-icon>more_horiz</mat-icon>
        </button>
        <mat-menu #appMenu="matMenu" xPosition="before">
            <ng-template matMenuContent let-aliasMenuItems="menuItems">
              <button mat-menu-item *ngFor="let item of aliasMenuItems" (click)="handleMenuAction(item.action)">
                {{item.text}}
              </button>
            </ng-template>
        </mat-menu>
      </div>
    </mat-toolbar>
  </div>
  <div>
    <router-outlet></router-outlet>
  </div>

Here is app.component.ts (parent). It retrieves the menu data from the appService component. It also (should) execute the callback.

  ngOnInit() {
    this.appService.getMenuData().subscribe(menuData => this.menuData = menuData);
  }

  handleMenuAction(action: Function) {
      action();
  }

Here is the child component "company.component.ts" which passes its menu items to app.service so they can be retrieved by app.component. Notice the menuData is an object that contains an array of objects of types string and callback function.

  ngOnInit(): void {

    this._appService.setMenuData({
      menuItems: [
        {text: "Add Company", action: this.addCompany}
    ]});
  }

  addCompany(): void {
    this._router.navigate(['/company', 0])
  }

For some reason the click event handler is not showing up in my Chrome dev tools. I would like the menu clicks to call functions, not just perform navigation.

There may be a better way to solve this problem. If so, please provide a link to an example. TIA.

Edit: Stackblitz is at https://stackblitz.com/edit/angular-nbzoe6

1
Provide a stackblitz for the same - Pujan Shah
stackblitz.com/edit/angular-nbzoe6 Disregard the "file not found" errors...they are false. - Dewey

1 Answers

0
votes

Updated Answer According to your stackblitz:

You just only need to bind the this of child component to new menu actions to run in child scope when you decide to call menu action

something like this:

company-list.component.ts

  ngOnInit(): void {
    this._appService.setMenuData({
      menuItems: [
        {text: "Add Company", action: this.addCompany.bind(this)}
    ]});
  }

Old Answer You can create the menuItems$ observable in your appService and subscribe on it in app.component.ts and from child component you just add new menuItems to this observable, The menuItems in your app.component will have new value

Something like this

appService.ts


class AppService {
  // ...
  menuItems$: BehaviourSubject<any[]> = new BehaviourSubject([]);
  constrcutor() {}

  // ...
}

app.component.ts

class AppComponenet {
  // ...
  menuItems: any[] = [];
  constrcutor(private appService: AppService) {}

  ngOnInit() {
    // ...
    this.appService.menuItems$.subscribe(newMenu => {
      this.menuItems = newMenu;
    });
  }
}

child.compnenet.ts

class ChildComponenet {
  // ...
  constrcutor(private appService: AppService) {}

  ngOnInit() {
    // ...
    this.appService.menuItems$.new(['home', 'about']);
  }
}