Devmix
Devmix

Reputation: 1858

How to always keep mat-menu at the same position?

I have this custom dropdown and I'm using mat-checkbox for the items to be selected. Anyway, my issue is that when I open the mat-menu and then I select items (let's select 2 items) at this time my mat-menu is at a specific position. Now if I close the mat-menu and open it again you'll see that the mat-menu is at a higher position (it moves like 5px up or so). Does anyone know how to always keep the mat-menu at the same position even after selecting items? Thanks a lot in advance.

NOTE: I tried using disableOptionCentering but it doesn't work on mat-menu only on mat-select

Here my LIVE DEMO

<mat-menu #menu="matMenu" class="menu-select">
    <div (click)="$event.stopPropagation()">
        <ng-container *ngTemplateOutlet="chipList">
        </ng-container>
        <div *ngFor="let topping of toppingList">
            <mat-checkbox [checked]="listOfItems && listOfItems.indexOf(topping)>=0"
                (change)="change(topping,$event.checked)">
                {{topping}}
            </mat-checkbox>
        </div>
    </div>
</mat-menu>

Upvotes: 0

Views: 1029

Answers (2)

Vega
Vega

Reputation: 28708

Debugging your code in stackblitz shows that you change the chip list in each selecting, leading to the recalculation of the dropdown panel height and position.
So the problem is more of a XY problem.
The code needs some refactoring of the array where you keep values. And use a pipe instead of a method that will be run on each click and reorder the ngFor. So here is my approach.

Typescript:

...
  toppingList = [
    { item: "item 1", selected: false },
    { item: "testing abc", selected: false },
    { item: "item 3", selected: false },
    { item: "Pepperoni", selected: false },
    { item: "Sausage", selected: false },
    { item: "Tomato", selected: false }
  ];
...



import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
  name: "myFilter",
  pure: false
})
export class MyFilter implements PipeTransform {
  transform(myArray: any[]) {
    return myArray.filter(item => item.selected);
  }


}

HTML:

<mat-menu #menu="matMenu" class="menu-select">
    <div (click)="$event.stopPropagation()">
        <ng-container *ngTemplateOutlet="chipList">
        </ng-container>
        <div *ngFor="let topping of toppingList">
            <mat-checkbox [checked]="topping.selected"
                (change)="topping.selected=!topping.selected">
                {{topping.item}}
            </mat-checkbox>
        </div>
    </div>
</mat-menu>
<ng-template #chipList>
    <mat-chip-list aria-label="Fruit selection">
        <mat-chip *ngFor="let fruit of (toppingList|myFilter)" removable="true">
            {{fruit.item}}
            <mat-icon matChipRemove (click)="fruit.selected=false">cancel</mat-icon>
        </mat-chip>
    </mat-chip-list>
</ng-template>

In this demo you can see that the menu always stays at the same position without any styling needed. Your code will be much lighter and readable

Upvotes: 1

JossFD
JossFD

Reputation: 2216

I'm not sure if you're doing a custom multiple select using the mat-menu component on purpose, but it isn't its intended use.

I'd recommend using the mat-select component with the multiple property. It easier to maintain and will also look much neater

Although you use the chips being displayed within the dropdown itself, I think that's not an issue, they shouldn't be inside the dropdown, it's not its purpose and makes it confusing from a UI/UX perspective.

I made a Stackblitz fork to illustrate.

Now the dropdown still gets displayed on top of the input (default behavior) to change that, have a look at the paneClass property. Here's a SO post

Upvotes: 0

Related Questions