Angular Material embed MatOption inside a component

Benjamin Maisonneuve
4 min readOct 25, 2020

I used the angular material library for few years and I love it. But I met a problem when I wanted to configure the MatSelect component with a set of options to reuse it anywhere.

Let me show you an example.

Let’s say you have multiple forms in your angular app and lot of them contains a property accessiblity. This accessiblity define who can access your object. The differents values can be PRIVATE (only you can access it), PUBLIC (anyone can access the object) and ADMIN (only the app admin can see it).

You can easily use the MatSelect componentwith the following options so the user can select the accessibility for the object object.

<mat-form-field>
<mat-select placeholder="accessibility">
<mat-option value="PUBLIC">PUBLIC</mat-option>
<mat-option value="PRIVATE">PRIVATE</mat-option>
<mat-option value="ADMIN">ADMIN</mat-option>
</mat-select>
</mat-form-field>

This will work but the problem is that if you want to use this accessibility attribute in another form, you’ll have to rewrite the <mat-select> with the list of options.

This is not good because you’ll have the list of MatOption are duplicated in different forms.

SelectWrapper

One solution could be to create a custom select component that contains the list of accessiblity options. The problem is that you’ll need implement ControlValueAccessor in the component in order to support NgModel and plug it to the form.

You’ll also need to create an input for each MatSelect inputs to be able to configure the MatSelect from outside your component (for example the required, disabled, multiple or compareWith)

Your component will look something like this (section SelectWrapper in the following stackblitz)

Moreover you’ll have a problem with the <mat-error>, it will not work if you embedded it in your component.

There is mutliple problem with the solution

  • You’ll have to redifined all the ControlValueAccessor methods
  • You’ll have to redefined all the MatSelect inputs to pass them to the MatSelect in your component
  • <mat-error> will not be supported

So how can we provide only the list of MatOption without rewriting or wrapping the MatSelect ?

Embedded options

We can try to create a component that only contains a list ofMatOption and pass this component inside the MatSelect. Here is the HTML of this component.

<mat-option value="PUBLIC">PUBLIC</mat-option>
<mat-option value="PRIVATE">PRIVATE</mat-option>
<mat-option value="ADMIN">ADMIN</mat-option>

Then we use this component inside the MatSelect.

<mat-form-field>
<mat-label>Accessibility</mat-label>
<mat-select [(ngModel)]="notWorking" placeholder="Accessibility">
<app-embedded-options-not-working></app-embedded-options-not- working>
</mat-select>
</mat-form-field>

See the following example in stackblitz

You will see the list of embedded options but you can’t select it. The MatSelect will not recognized the options inside the EmbeddedOptionsNotWorkingComponent. This is because the MatSelect component uses @ContentChildren to access the list of MatOption. And because the MatOptions are embedded in the EmbeddedOptionsNotWorkingComponent component, the MatSelect can’t access it.

Embedded options with registration

The solution is to manually register the embedded options in the MatSelect (to tell the MatSelect what are the available options). To do this we can access the QueryList of MatOption inside the MatSelect and call the method reset. By calling this method with our list of options, we can provide the list of embedded options to the MatSelect.

We can get the MatSelect instance with the @Host decorator bacause this component is used inside the MatSelect.

We can access the list of MatOption of the EmbeddedOptionsWorkingComponent with a @ViewChildren. We subscribe to this QueryList to get the list of our options. Then we have to pass these options to the MatSelect list of options . We call the reset method of the select.options (which is a QueryList) to tell the MatSelect what are the options.

Then we have to notify the QueryList of MatSelect that the options have changed by calling the method select.options.notifyOnChanges.

Et voilà ! Your options embedded in the component are now recognized by the MatSelect. You can reuse the EmbeddedOptionsWorkingComponent inside another select of another form with different options (like multiple).

See this working example in the follwing stackblitz

In the options component you can also call an API to retrieve the options form a database.

Create an abstraction

Now that we managed to embed MatOptions inside a component, we can create an abstract class to simplify the registration of the options so we can reuse it for another set of options.

Here is the abstract class to simplify options registration.

A component can extends this class to make the options work. It just need to passes the MatSelect instance (accessed with the @Host) and call the method init option in the AfterViewInit method.

You can find a running example in the last section of the following stackblitz.

In this example, the AbstractEmbeddedOptionsDirective is more complete and can be used inside a MatAutocomplete. There is also a method to init the MatSelect with a value or to override the compareWith if your MatOption are objects (and not only strings).

You can find the the complete example in github : https://github.com/BobBDE/material-select-option-embedded

Hope this will help you to reuse MatSelect !

--

--

Benjamin Maisonneuve

Freelance web developer using angular and angular material since v2