问题
How can I add dynamically empty mat-option to all not required mat-select in my app.
like :
 <mat-form-field appearance="outline">
         <mat-label>HMO</mat-label>
         <mat-select>
              <--how can I add this dynamically-->
              <mat-option>--</mat-option>
              <mat-option *ngFor="let hmo of HMOList"
                              [value]="hmo.Code">
                    {{ hmo.Desc }}
              </mat-option>
         </mat-select>
  </mat-form-field>
any idea?
回答1:
I used the response from Prince, but my model didn't update. And I added the following in OptionalMatSelectDirective
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;
    }
    const matOption = this.renderer.createElement('mat-option');
    this.renderer.setAttribute(matOption, 'class', 'mat-option');
    this.renderer.listen(matOption,'click', ()=>
    {
      this.matSelect.value = '';
      **this.matSelect.ngControl.viewToModelUpdate(undefined);**
      this.matSelect.close();
    });
    const panel= document.querySelector('.mat-select-panel');
    if(!panel)
    {
      throw new Error('Cannot find mat select panel');
    }
    this.renderer.insertBefore(panel,matOption, panel.firstChild);
  }
}
And, it worked for me!
回答2:
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);
  }
}
    回答3:
Why don't just modify HMOList behind the scene like HMOList = [ ...HMOList, { code: 'None', Desc: ''} ] (or other way around as You want the default option first)? It seems a little bit hacky but it is also seems to be the easiest one. Or better store additional array for mat-select if we want to keep the original array as we copy everything over either way to make sure UI and whatever else will be updated.
回答4:
There are multiple ways to accomplish this task,
First approach first is you can create a property in component class and control the visibility of default mat-option. And bind this property in template like below:
Template
  <mat-select [required]="isRequired">
    <mat-option *ngIf="isRequired">---</mat-option>
    <mat-option *ngFor="let food of foods" [value]="food.value">
      {{food.viewValue}}
    </mat-option>
  </mat-select>
Component class
export class SelectOverviewExample {
  isRequired = true;
  foods: Food[] = [
    {value: 'steak-0', viewValue: 'Steak'},
    {value: 'pizza-1', viewValue: 'Pizza'},
    {value: 'tacos-2', viewValue: 'Tacos'}
  ];
}
Please use the following working link - https://stackblitz.com/edit/angular-azkmrp
Second approach Second approach is you can use template reference variable in template only and use it to add default mat-option. Here I have created a #matselect template reference variable and added the required attribute. Using this template reference variable with the default mat-option. Just toggle the required attribute to test.
 <mat-select #matselect required>
    <mat-option *ngIf="matselect.required">---</mat-option>
    <mat-option *ngFor="let food of foods" [value]="food.value">
      {{food.viewValue}}
    </mat-option>
  </mat-select>
Please find working code here: https://stackblitz.com/edit/angular-zpvjf2
回答5:
You can use javascript unshift function to add first value for all required select elements.
HMOList.unshift( {code:'', desc:'--'});
If you want to add same kind of option in every select box. I have made changes in stackblitz.
See here
回答6:
I can get you this far:
- Create a new directive with selector as 
mat-select - Get a reference to the MatSelect element via 
ContentChildren - I could use this to add an option to the 
_resultsarray on theMatSelectbut it doesn't update the DOM, so you need to figure that one out 
This does not require any change to HTML.
Below is as far as I could get in a short amount of time. There will be a better way to add an option to the MatSelect - you basically need to look at the Angular source files and see how they add a MatOption and then replicate this in your Directive.
Good luck!
@Directive({
  selector: 'mat-select',
  exportAs: 'myMatSelect'
})
export class MyMatSelectDirective {
  constructor(
    public changeDetectorRef: ChangeDetectorRef
  ) { }
  select: MatSelect;
  @ContentChildren(MatSelect) set list(select: QueryList<MatSelect>) {
    this.select = select.first;
    // One possible option is to subscribe to the openChange event
    this.select.openedChange.subscribe(open => {
      let opt = document.createElement("mat-option");
      opt.innerHTML = "--";
      (<any>this.select.options)._results.unshift(new MatOption(new ElementRef(opt), this.changeDetectorRef, this.select, null));
      console.log(this.select)
    })
  }
}
    来源:https://stackoverflow.com/questions/60949388/angular-add-empty-mat-option-to-all-not-required-mat-select-in-my-app