4
votes

I am trying to create a dynamic menu using a json response from server but I am getting this error:

MenuComponent.html:4 ERROR TypeError: Cannot read property 'subscribe' of undefined at MatMenuTrigger.push../node_modules/@angular/material/esm5/menu.es5.js.MatMenuTrigger.ngAfterContentInit

and when I click the buttons it says this:

ERROR TypeError: Cannot read property 'createEmbeddedView' of undefined at ViewContainerRef_.push../node_modules/@angular/core/fesm5/core.js.ViewContainerRef_.createEmbeddedView

I am guessing that buttons can't created because json response is not ready but I don't know how to fix it.

Component.ts

export class MenuComponent implements OnInit {
  branchList: Branches = new Branches(); //read somewhere that I need to initialize, not sure

  ngOnInit(): void {
    this.http.get('http://demo8635782.mockable.io/branches').subscribe((data: any) => {
      if (data === null) {
        console.log('api request returns empty!');
      }
      this.branchList = data;
    });
  }

  constructor(private breakpointObserver: BreakpointObserver, private http: HttpClient) {
  }
}

Template.html

<button mat-button [matMenuTriggerFor]="branchesMenu">Branches</button>
<mat-menu #branchesMenu="matMenu">
  <div *ngFor="let branch of branchList?.branches">
    <button mat-menu-item [matMenuTriggerFor]="branch?.name">{{branch.name}}</button>
  </div>
</mat-menu>

Stackblitz

3
the line you're unsure about.. what about changing from branchList: Branches = new Branches(); to branchList: Branches; ? does it change the error? ... also I've never seen a type declared for ngOnInit- there is no needKyle Burkett
nope, it doesn't change anything, I saw it from here: linkCagurtay
Please create an minimal reproducible example, I recommend stackblitz.comIgor
@Igor I added the exampleCagurtay
Interface of returned data must not be similar to interface of branchList, and that is creating error.Abhishek Jaiswal

3 Answers

3
votes

You're overcomplicating it. It's just a matter of the return type of HttpClient.get. Apparently it's not returning an Observable, and you'll need to read the docs for the library to see why.

You could also simplify the code a great deal:

export class AppComponent  {
  readonly branches = this.http
    .get('https://demo8635782.mockable.io/branches')
    .pipe(
    map((data) => data as Branches),
    map(({branches}) =>branches),
    shareReplay(),
  );    
  constructor(private http: HttpClient) {}
}


<button mat-button [matMenuTriggerFor]="branchesMenu">Branches</button>
<mat-menu #branchesMenu="matMenu">
 <button mat-menu-item *ngFor="let branch of (branches | async)">{{branch.name}}</button>
</mat-menu>

EDIT: nope, totally wrong. As Daniel Caldera pointed out below, the actual problem was the matMenuTriggerFor of the mat-menu-item.

Other problems:

  1. You need to import BrowserAnimationsModule
  2. You need to import the operators (map, shareReplay)
  3. You need to include the theme in CSS
  4. You shouldn't de-reference an async, as I originally suggested

Here is my working version.

3
votes

I solved it in this stackblitz I use the correct version of material and angular updating the dependencies, use the observable in the template wrapping the button in a ngIf, also deleted the matMenuTriggerFor of the mat-menu-item and import the BrowserAnimationsModule in the app.module.

Edit for adding subMenu, for it you must create the submenu inside the ngFor iteration.

0
votes

I had similar problem. Made a login to refresh my token. The backend expects token so the request has to be made with a valid token.

Make sure you've valid token in your request.

Review your backend.