Khepf
Khepf

Reputation: 382

Dynamically position Angular Material 13 mat-select dropdown without affecting autocomplete

Lots of people seem to have this issue but I haven't found a solution that doesn't create problems in other areas.

How to customize mat-select dropdown position?

.cdk-overlay-pane {
  position: absolute;
  pointer-events: auto;
  box-sizing: border-box;
  z-index: 1000;
  display: flex;
  max-width: 100%;
  max-height: 100%;
  transform: none !important;
  margin-top: 30px;
}

This one works great. ...except that it also affects mat-autocompletes, and not in a good way. It lines up the mat-select options perfectly, and moves the mat-autocomplete options down 30px. Not good.

So I thought, maybe isolate the css to mat-select somehow. There are no parent classes unique to mat-select, so anything :child won't work. There is a direct child class though called mat-select-panel-wrap. So I looked for a way to access the parent via this unique child. :has is the only thing I found that claims to do this, but alas, zero browser support.

https://caniuse.com/css-has

Back in 11 and earlier, Angular Material had a great overlayDir: CdkConnectedOverlay; that allowed for changes to the options box positioning. But they depreciated it and then made it protected, without fixing the problem that was making people use it in the first place. And their "solution" was "try our experimental mat-select." That solution is dubious at best.

MatSelect overlayDir now private when updating from Angular 11 to 12. How do I access it now?

My question is, does anyone know of a way to isolate the above css to just mat-selects? Or more specifically, to just the mat-select options box.

Upvotes: 1

Views: 2008

Answers (2)

Eliseo
Eliseo

Reputation: 57919

The position of the mat-select are defined by his private property _positions, see the github

You can change it. If you has, e.g.

<!--see the template reference variable "select"-->
<mat-select #select>
    ...
</mat-select>

You can get it using viewChild and change this property. I know that is a private property, so first you "cast to any" to do it

e.g. If you want to show to the right

  @ViewChild('select',{static:true}) select:MatSelect

  ngOnInit(){
    //add a new _positions 
    (this.select as any)._positions.unshift(
      {
        originX: 'end',
        originY: 'top',
        overlayX: 'start',
        overlayY: 'top',
        offsetX:5
      })
  }

See a stackblitz

NOTE: See that I choose add using unshift -you can also defined all the positions-. In this way, if select have no enough space -in the stackblitz you can make the screen narrowest-, the select it's move to down

About position strategy, see this Netanel Basal entry blog. See that it's a bit old and it's added new properties: offsetX and offsetY, see the docs in material angular

Update why not create a directive?

export const POS: {
  top: ConnectedPosition;
  right: ConnectedPosition;
  bottom: ConnectedPosition;
  left: ConnectedPosition;
} = {
  top: {
    originX: 'start',
    originY: 'top',
    overlayX: 'start',
    overlayY: 'bottom',
    offsetY: -5,
    offsetX: 0,
    panelClass: '',
    weight: 120,
  },
  right: {
    originX: 'end',
    originY: 'top',
    overlayX: 'start',
    overlayY: 'top',
    offsetX: 5,
    offsetY: 0,
    panelClass: '',
    weight: 120,
  },
  bottom: {
    originX: 'start',
    originY: 'bottom',
    overlayX: 'start',
    overlayY: 'top',
    offsetY: 5,
    offsetX: 0,
    panelClass: '',
    weight: 120,
  },
  left: {
    originX: 'start',
    originY: 'top',
    overlayX: 'end',
    overlayY: 'top',
    offsetX: -5,
    offsetY: 0,
    panelClass: '',
    weight: 120,
  },
};
@Directive({
  selector: 'mat-select[position]'
})
export class MatSelectPositionDirective implements AfterViewInit {
  @Input('position') position:'top'|'bottom'|'left'|'right'
  constructor(@Host() private select:MatSelect){}
  ngAfterViewInit()
  {
    (this.select as any)._positions.unshift(POS[this.position])
  }
}

You can use like

<mat-select [position]='top'>
...
</mat-select>

Upvotes: 4

Mahesh tamarakar
Mahesh tamarakar

Reputation: 69

Shortly, add providers to your component like the followings:
and don't forget to import MAT_SELECT_CONFIG you will find this customClass⭐ with .cdk-overlay-pane class now you can specifically target your mat_select component

BONUS TIP: try to use emoji to find the class name while inspecting

import { MAT_SELECT_CONFIG } from '@angular/material/select';

@Component({
  selector: 'app-seller-home',
  templateUrl: './seller-home.component.html',
  styleUrls: ['./seller-home.component.css'],
  providers: [
    {
      provide: MAT_SELECT_CONFIG,
      useValue: { overlayPanelClass: 'customClass⭐' }
    }
  ],
})

Upvotes: 0

Related Questions