Search code examples
angulartypescriptmat-select

How do I programmatically set a MatSelect's MatSelectTrigger?


Given a reference to a MatSelect instance, how do I go about programmatically setting its MatSelectTrigger template? Per the documentation, there is a settable customTrigger property, but I wasn't able to find any documentation on the MatSelectTrigger class or how to dynamically create one.

My goal is to have a directive that sets up a <mat-select-trigger> component on a MatSelect dynamically to reduce copy+paste.

In the meantime I am manually manipulating the DOM on the selectionChange event, but this is hacky and prone to breaking.

Is there an official and safe way to, inside a directive and with a reference to a MatSelect, give it a dynamic trigger template?

Thanks!


Solution

  • there is a settable customTrigger property, but I wasn't able to find any documentation on the MatSelectTrigger class or how to dynamically create one.

    customTrigger is not a settable property. It's a @ContentChild type. This means that the mat-select expects a content projection of type MatSelectTrigger.

    This is the excerpted code from the source file:

     @ContentChild(MAT_SELECT_TRIGGER) customTrigger: MatSelectTrigger;
    

    Solution

    To dynamically supply a different template into the mat-select-trigger, you will need to create a wrapper component as following:

    mat-select-wrapper.component.html

    <mat-form-field>
      <mat-label>Toppings</mat-label>
    
      <mat-select [formControl]="toppings" multiple>
    
        <mat-select-trigger>
          <!-- NOTICE HERE: We inject triggerTemplate into mat-select-trigger -->
          <ng-container *ngTemplateOutlet="triggerTemplate"></ng-container>
        </mat-select-trigger>
    
        <mat-option *ngFor="let topping of toppingList" 
                    [value]="topping">{{topping}}
        </mat-option>
    
      </mat-select>
    </mat-form-field>
    

    mat-select-wrapper.component.ts

    import {Component, Input, TemplateRef} from '@angular/core';
    import {FormControl} from '@angular/forms';
    
    @Component({
      selector: 'app-mat-select-wrapper',
      templateUrl: 'mat-select-wrapper.component.html',
      styleUrls: ['mat-select-wrapper.component.css'],
    })
    export class MatSelectWrapperComponent {
    
      // Accept a custom template from outside and display it.
      @Input()
      public triggerTemplate: TemplateRef<any>;
    
      public toppings = new FormControl();
    
      public toppingList = ['Extra cheese', 'Mushroom', 'Onion'];
    }
    

    Parent component that uses the wrapper

    <app-mat-select-wrapper [triggerTemplate]="myTemplate"></app-mat-select-wrapper>
    
    <ng-template #myTemplate>
      Hello! You can provide any kind of HTML here.
    </ng-template>
    

    You will need to add more @Input so you can pass in different values through the wrapper and into the actual mat-select. Such as toppingList that is used by the mat-option.