Here is a stackblitz example, based on the various answers here.
The directive below is applied to mat-select, so you do not have to add any extra attributes. It uses the required attribute on the select to know whether to add any blank option when the dropdown opens.
Note: You cannot easily add items to the underlying list of mat options used by the mat-select component, so the mat options elements are added manually, and events need to be bound manually too (I did it for the click event);
import { Directive, HostListener, OnInit, Renderer2, ElementRef, AfterViewInit } from '@angular/core';
import { MatSelect } from '@angular/material/select';
import { Directive, HostListener, OnInit, Renderer2, ElementRef, AfterViewInit } from '@angular/core';
import { MatSelect } from '@angular/material/select';
@Directive({
selector: 'mat-select'
})
export class OptionalMatSelectDirective {
constructor(private matSelect: MatSelect, private elementRef: ElementRef,
private renderer: Renderer2) { }
@HostListener('openedChange', ['$event'])
onOpenedChange( isOpened : boolean)
{
if(!isOpened || this.matSelect.required)
{
return; //Closing panel, or select is required
}
//Manually create a mat option DOM element
let matOption = this.renderer.createElement("mat-option");
this.renderer.setAttribute(matOption, 'class', 'mat-option');
//Bind events to the new mat option
this.renderer.listen(matOption,'click', ()=>
{
this.matSelect.value = "";
this.matSelect.close();
});
//Try to add the new mat option in first position of the list
let panel= document.querySelector('.mat-select-panel');
if(!panel)
{
throw "Cannot find mat select panel";
}
this.renderer.insertBefore(panel,matOption, panel.firstChild);
}
}