Can I pass mat-options to my custom mat-select component using ng-content?

I created a custom select component and attempted to utilize ng-content to pass in my options. Here is the code snippet I used:

<lib-select [(selected)]="selected" (selectedChange)="onChange($event)">
    <mat-option [value]="0">Value 1</mat-option>
    <mat-option [value]="1">Value 2</mat-option>
    <mat-option [value]="2">Value 3</mat-option>
    <mat-option [value]="3">Value 4</mat-option>
    <mat-option [value]="4">Value 5</mat-option>
</lib-select>

However, this approach did not work as expected. The options were not displayed initially. Even after applying a workaround to show them, I still couldn't select anything. Below is the code snippet of my component:

<mat-select panelClass="select" disableRipple (selectionChange)="onChange()" [(value)]="selected" disableOptionCentering>
    <mat-select-trigger>{{selected}}</mat-select-trigger>
    <!-- A hidden mat-option below is necessary for rendering ng-content in mat-select. This is more of a hack than an ideal solution -->
    <mat-option [value]="" style="display: none;"></mat-option>
    <ng-content></ng-content>
</mat-select>

Is there a way to make this setup function correctly or does mat-select simply not support ng-content?

I'm aware that I could use @Input() to send the options into the component, but I prefer the cleaner look achieved with ng-content.

UPDATE: It turns out I am able to select items. The issue lies in the fact that I can select multiple options and see a ripple effect, despite having disableRipple set on my mat-select element.

Answer №1

If you encounter an issue, there is a workaround available. Place the ng-content inside a hidden div and create the necessary options by using ContentChildren(MatOption). You can refer to the sample code in this StackBlitz example

Below is the component:

import {Component, ContentChildren, AfterViewInit, QueryList} from "@angular/core";
import { MatOption } from "@angular/material/core";

@Component({
  selector: "custom-select",
  template: `
    <mat-form-field>
      <mat-label>Favorite food</mat-label>
      <mat-select>
        <ng-container *ngIf="yet">
          <mat-option *ngFor="let option of options" [value]="option.value">
            {{ option.viewValue }}
          </mat-option>
        </ng-container>
      </mat-select>
    </mat-form-field>
    <div style="display:none" *ngIf="!yet">
      <ng-content></ng-content>
    </div>
  `
})
export class CustomSelect implements AfterViewInit {
  @ContentChildren(MatOption) queryOptions: QueryList<MatOption>;
  options: any[];
  yet: boolean;
  ngAfterViewInit() {
    this.options = this.queryOptions.map(x => {
      return { value: x.value, viewValue: x.viewValue };
    });
    setTimeout(() => {
      this.yet = true;
    });
  }
}

Here's how you can use it:

<custom-select>
    <mat-option *ngFor="let food of foods" [value]="food.value">
      {{food.viewValue}}
    </mat-option>
</custom-select>

Answer №2

Expanding on the insights shared by @Eliseo and addressing the query posed by @SadHippo123 regarding the vacant viewValue:

One does not necessarily need to utilize the yet property for *ngIf on mat-option, as @ContentChildren specifically targets children within ng-content.

To resolve the issue with the empty viewValue, instead of assigning values in the afterviewinit hook, you can subscribe to the QueryList.changes observable and assign them there - this approach successfully addressed the problem for me:

ngAfterViewInit(): void {
    this.queryOptions.changes.pipe(
      takeUntil(this.destroy$),
      tap((changes: MatOption[]) => {
        this.transformedOptions = changes.map((option) => {
          return { value: option.value, viewValue: option.viewValue }
        });
      })
    ).subscribe();
  }

I also crafted a personalized mat-select component equipped with extra functionalities. Here's my version for those interested:

CustomSelectDropdown.ts

[Unique rephrased content]

CustomSelectDropdown.html

[Unique rephrased content]

If pasting the code renders it oddly formatted, it is due to the separate styling applied to mat-options/mat-select in another file. This snippet focuses more on functionality than appearance.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Converting input/select string values into strongly-typed values with Angular 2

I've been searching for a solution for quite some time now, but I'm still a bit confused. The issue is simple: I have a model with a boolean property that I'm mapping to a select element in Angular. The select allows the user to choose betwe ...

Angular 6 form template issue: 'No directive found with "exportAs" attribute set to "ngForm"'

I recently downloaded a free skin for Angular 6 + Bootstrap 4 from here. However, I keep encountering the following error message: "There is no directive with 'exportAs' set to 'ngForm'." In my app.module.ts file: import {CommonModule ...

Is it possible to programmatically create a button to toggle the expansion and collapse of the Column Grouping in AG-Grid?

Looking to add a button that can expand/collapse all Grouped Columns programmatically, similar to the existing expand/collapse icon but for all column groups without relying on the pivot panel. Check out the Column Expand/Collapse icon here I searched th ...

Production is experiencing a hiccup, however, the site is still up and running. There seems to be

Having an error in production that I can't seem to replicate on my local machine. The error message reads: src/controllers/userController.ts(2,29): error TS2307: Cannot find module '../services/UserService' or its corresponding type declarat ...

Start Angular application using SSL encryption

I am striving to launch my Angular (7+) project with an SSL certificate on localhost (https://localhost:4200). Following the guidance provided in this source - Get angular-cli to ng serve over HTTPS, I attempted the following steps: 1) angular.json { " ...

Encountering problems with createMediaElementSource in TypeScript/React when using the Web Audio API

Currently, I am following a Web Audio API tutorial from MDN, but with a twist - I am using TypeScript and React instead of vanilla JavaScript. In my React project created with create-react-app, I am utilizing the useRef hook to reference the audio element ...

Encountering a problem while trying to incorporate Mapbox GL JS into an Angular 8 web application

I'm currently working on incorporating mapbox into my simple web application, but I'm encountering difficulties when attempting to add it. At this point, I've already created a mapbox service and a map component. My approach involved using ...

Troubleshooting Problem with Installing Angular2-Google-Maps Component in FountainJS Application

Using the FountainJS Angular2 generator with Typescript and Systems.js has been helpful for scaffolding my project. Check it out here However, I encountered an issue while trying to add a component to the project. Upon importing {GOOGLE_MAPS_DIRECTIVES}, ...

Error in Angular: Unexpected '<' token

After setting up my angular and express app using the angular-cli and express command lines, I successfully built the angular app with the ng build command. However, when attempting to serve it with the express server, I encountered the following error in ...

Angular 4+ directive allowing interaction with the NgModel of a component

I'm looking to update styles based on the state of NgModel.control. To keep it DRY, I was thinking that a directive for reading the NgModel component state could be the solution. Is this actually feasible? I haven't been able to find any guidanc ...

Mocha is considering introducing experimental support for decorators in an upcoming release, though this feature may be modified before its official implementation

When attempting to perform a unit test using Mocha on Windows, I used the command below: mocha --require ts-node/register test.spec.ts An error occurred, showing: error TS1219: Experimental support for decorators is a feature that is subject to change i ...

Is it possible to swap images by clicking on them?

Let's say I'm working with 3 images. Image A, B and C. A is the main image initially. If I click on image B, it will become the main image and image A will take its place in the old position. The same condition applies when B is the main image a ...

Is there a way in Typescript to determine the type of a union type when a specific condition is satisfied?

I am faced with a scenario where I have an object that can take on two different shapes: {ageTop:42} or {locations: [{name:Myrtle Beach}]} These objects are passed as parameters to a function, and I want to ensure that the function only receives the t ...

Angular Material UI: Nested Table Component

I am dealing with a table that has four static headers and two static columns. The data in the last two columns changes based on certain conditions. I need to convert this table into an Angular Material table. The first table is a standard HTML table. Th ...

Versatile unit equipped with interactive features

As a newcomer to Angular, I have a project in mind to create a versatile confirmation popup component that can serve multiple purposes. Whether it's for deleting or saving an entry, users will need to confirm the action with a simple popup that includ ...

Pattern matching for directory path excluding the filename

Looking for assistance with creating a JavaScript regex pattern to validate text input as folder paths only, excluding the filename. Can someone provide guidance on this? For example: folder1/folder2/folder3 => valid folder1/folder2/filename.txt1 =&g ...

What is the best way to send data to the ng2-smart-table renderComponent using an HTTP request?

I am working with a table that includes a custom component in one of its cells. There is also a service that provides the data to display in this table. The custom component I have implemented includes a select feature, so the column in the table looks lik ...

Jest: A guide on mocking esModule methods

In my code, I have a function that utilizes the library jszip to zip folders and files: // app.ts const runJszip = async (): Promise<void> => { const zip = new Jszip(); zip.folder('folder')?.file('file.txt', 'just som ...

Utilize ngx-select-dropdown to showcase various displayKey values at once

I am currently utilizing the ngx-select-dropdown plugin with a search feature, and I am looking to specify multiple values for the displayKey property such as: firstname and lastname. Here is the configuration object I am using: dropdownconfig = { di ...

Changing TypeScript Enum from String to Number in Angular

Does anyone know how to convert a Typescript enum value to a number for display in a dropdown but passing the numeric value on submit? Here is how the enum is currently set up: I am currently able to output the string key of the object when it is emitted ...