Is there a way to prevent mat-select usage in TypeScript through a directive?

Working with Angular 9 / typescript and looking to disable specific form elements based on a certain condition. I came across a helpful example where all elements are disabled, but noticed that MAT-SELECT remains active. How can I disable it?

https://i.sstatic.net/j9Khp.jpg

The original example code:

private disableElement(element: any) {
    if (this.appDisable) {
      if (!element.hasAttribute(DISABLED)) {
        this.renderer.setAttribute(element, APP_DISABLED, '');
        this.renderer.setAttribute(element, DISABLED, 'true');

        // disabling anchor tab keyboard event
        if (element.tagName.toLowerCase() === TAG_ANCHOR) {
          this.renderer.setAttribute(element, TAB_INDEX, '-1');
        }
      }
    } else {
      if (element.hasAttribute(APP_DISABLED)) {
        if (element.getAttribute('disabled') !== '') {
          element.removeAttribute(DISABLED);
        }
        element.removeAttribute(APP_DISABLED);
        if (element.tagName.toLowerCase() === TAG_ANCHOR) {
          element.removeAttribute(TAB_INDEX);
        }
      }
    }
    if (element.children) {
      for (let ele of element.children) {
        this.disableElement(ele);
      }
    }
}

My modified code snippet:

private disableElement(element: any) {
    if (this.appDisable) {
      if (element.tagName == "INPUT" || element.tagName == "MAT-SELECT" || element.tagName == "BUTTON") {
        if (!element.hasAttribute(DISABLED)) {
          this.renderer.setAttribute(element, APP_DISABLED, '');
          this.renderer.setAttribute(element, DISABLED, 'true');

          // disabling anchor tab keyboard event
          if (element.tagName.toLowerCase() === TAG_ANCHOR) {
            this.renderer.setAttribute(element, TAB_INDEX, '-1');
          }
        }
      }
    } else {
      if (element.tagName == "INPUT" || element.tagName == "MAT-SELECT" || element.tagName == "BUTTON") {
        if (element.hasAttribute(APP_DISABLED)) {
          if (element.getAttribute('disabled') !== '') {
            element.removeAttribute(DISABLED);
          }
          element.removeAttribute(APP_DISABLED);
          if (element.tagName.toLowerCase() === TAG_ANCHOR) {
            element.removeAttribute(TAB_INDEX);
          }
        }
      }
    }
    if (element.children) {
      for (let ele of element.children) {
        this.disableElement(ele);
      }
    }
}

Answer №1

One issue that arises is the difference between a mat-select and an input select element

If you are utilizing ReactiveForms, you have the option of creating a custom directive that makes use of the enable() and disable() methods from FormControls

An example of such a directive would be:

import {Directive, Input} from "@angular/core";
import { FormGroupDirective } from "@angular/forms";

@Directive({
  selector: "[appDisable]"
})
export class DisableDirective {
  constructor(private fgd: FormGroupDirective) {}
  @Input() set appDisable(value: boolean) {
    Object.keys(this.fgd.form.controls).forEach(x => {
      const control = this.fgd.form.get(x);
      if (control) {
        if (value) control.disable();
        else control.enable();
      }
    });
  }
}

This directive can then be used within a form like so:

<form [formGroup]="form" [appDisable]="disabled">
    <mat-form-field appearance="fill">
        <mat-label>Favorite food</mat-label>
        <mat-select formControlName="food">
            <mat-option *ngFor="let food of foods" [value]="food.value">
                {{food.viewValue}}
            </mat-option>
        </mat-select>
    </mat-form-field>
    <mat-form-field appearance="fill">
        <mat-label>Name</mat-label>
        <input matInput formControlName="name"/>
    </mat-form-field>
</form>
<button mat-button (click)="disabled=!disabled">{{disabled?'Enable!':'Disable!'}}</button>

Refer to this stackblitz for a live example

Update: How to access a button or NgControls To access a button or NgControl, we need to utilize ContentChild and ContentChildren. We declare two variables as follows:

  @ContentChild(MatButton) button: MatButton;
  @ContentChildren(NgControl, { descendants: true }) controls: QueryList<NgControl>;

Accessing a ContentChild requires waiting until ngAfterViewInit before translating the code into a function. Additionally, the function accounts for scenarios where reactive Forms are not being used by disabling the controls. Here's how it looks:

  setEnabled(value: boolean) {
    if (this.fgd) {
      Object.keys(this.fgd.form.controls).forEach(x => {
        const control = this.fgd.form.get(x);
        if (control) {
          if (value) control.disable();
          else control.enable();
        }
      });
    } else {
      if (this.controls) {
        this.controls.forEach(x => {
          const control = x.ngControl;
          if (control) {
            if (value) control.disable();
            else control.enable();
          }
        });
      }
    }
    if (this.button) this.button.disabled = value;
  }

To implement this functionality, call the function in ngAfterViewInit and in the setter, while also introducing a new private variable _disable:

  @Input() set appDisable(value: boolean) {
    this._disable = value;
    this.setEnabled(value);
  }
  ngAfterViewInit() {
      this.setEnabled(this._disable);
  }

It's worth noting that the stackblitz has been updated with all these changes

Answer №2

The code snippet provided above is tailored for a standard select element, not relevant to mat-select with a material directive.

When using ViewChild, the element retrieved is of type MatSelect which does not possess a nativeElement property. This leads to an undefined value in your specific scenario.

However, consider utilizing the following approach:

In your HTML template

<mat-select [disabled]=isDisabled >
    <mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
  </mat-select>

Within your component, you have the flexibility to toggle the "isDisabled" property based on certain conditions.

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

Tips on accessing InnerText with VUEJS

I'm struggling with displaying the innerText generated by my generatePseudonym() function in a modal dialog. To better illustrate, here is a screenshot of what I mean: https://i.sstatic.net/pEl5P.png I am aiming to show the output Anastasia Shah as th ...

Error in Angular 9 Component Test: Unable to locate export named 'ngbNav'

I'm encountering an issue with a modal component that I need to test. Every time I try running the test, it gives me this error: "Export of name 'ngbNav' not found". Any suggestions on how to resolve this would be greatly appreciated. Error ...

Converting an array into an array of objects using Node.js

I currently have a Node.js REST API that generates an array structured like this: [["Benni", 24, "Whatever"], ["Paul", 23, "Whatever"]] In order to integrate this array into an Angular Material table, I need to transform it into the following format: [{ ...

I am facing an issue with Angular's ngFor where it is not displaying the data fetched from my Spring Boot application on the browser

While I was working on establishing connectivity between SpringBoot and Angular, I encountered an issue where the data fetched using getmapping in Spring would successfully print on the server side. However, when passing this data to Angular through a serv ...

Issue with the display of JQuery slider within TypeScript Angular directive in C# environment

I am encountering an issue with implementing a JQuery slider in my application. When I use it solely with JQuery, it functions properly. However, when I incorporate it into an angular directive built with typescript, the display is not as expected. https: ...

The error message "NullInjectorError: No provider for HTTP!" is generated by the ionic-native/http module

Currently working with ionic 3.2 and angular. To install the HTTP module (https://ionicframework.com/docs/native/http/), I used the following commands: ionic cordova plugin add cordova-plugin-advanced-http npm install --save @ionic-native/http In my scri ...

Switch from manipulating the DOM to using Angular binding to update the td element with fresh information

When I click the edit button on a tr Element, the tdElement for the redirectUrl should become editable. By using the id of the tdElement and changing the contenteditable attribute to true, I can then use JQuery to retrieve the newly typed data. <tr ng- ...

Saving the compiled Angular dist folder in the artifactory repository

Our team utilizes Artifactory to manage libraries and artifacts. We have configured the npm registry to point to our Artifactory URL for library retrieval. GitLab is integrated into our CI pipeline. Specifically, I have implemented a job that compiles the ...

Using Angular's NgFor directive to loop through a list of items and trigger a click event

Is it possible to programmatically trigger a click event on a specific item within an ngFor loop? <ul> <li class="list" *ngFor="let ver of versions; (click)="versionView()">{{ver.name}}</li> </ul> ...

Issues resolving the signature of a parameter in a Typescript decorator within vscode

I encountered an error while attempting to decorate a class in my NestJS service. The Typescript code compiles without any issues, but I am facing this problem only in VSCode. Unable to resolve signature of parameter decorator when called as an expression ...

Determining the generic type from supplied Record values in TypeScript: A guide

There is a function called polymorficFactory that creates instances of classes based on a provided 'discriminator' property: type ClassConstructor<T> = { new (...args: any[]): T; }; type ClassMap<T> = Record<string, ClassConstr ...

Utilize the MD_CARD component exclusively from Material Design 2, omitting the rest of the features from the Material

Is it possible to selectively use certain material design components without having to include the entire library? I am particularly interested in utilizing MD_CARD_DIRECTIVES. ...

Display popup when the form is submitted

Check out this Angular 4 component code designed for gathering contact details from website visitors: .html: <form (submit)="onCreateContact()"> <div class="form-group"> <input type="text" [(ngModel)]="contactname" name="contac ...

Leveraging Observables with ngrx for efficient async pipe implementation

Trying to create a shadow copy of pending changes using observables and ngrx has presented me with a puzzling issue: export class SearchBoxContainerComponent { filterSettings$: Observable<FilterSettings>; filterChanges: {[key:string]: any}; ...

Destructuring an array of strings for use as parameters

Hey guys, I'm working with an array of keys here Example 1: let keyArray = ['x', 'y', 'z'] I'm trying to find a way to use these keys as parameters without repeating them multiple times. Do you have any suggestions ...

Updating to Angular 2's latest release, rc5

Using forms provided in rc5 has been a challenge for me, as updating to that version is difficult. I attempted to follow a tutorial at but it was not compatible with rc3. Below is my boot.ts file: import { bootstrap } from '@angular/platform-browse ...

Switch up row values in an array and transform them into an object using SheetJS

I am struggling to format an array where each "Working Day" is represented as an object with specific details like index and start/end date. I need help manipulating the JSON data to achieve the desired structure. The package I'm currently using is: ...

Unable to receive a response from the HttpClient

I've encountered an issue while trying to manage error responses from my API. What I expect to receive: What I'm actually receiving: My service.ts: import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Inject ...

Angular 10 - unable to bind 'formGroup' as it is not recognized as a valid property of 'form'

In my existing Angular application, I need to implement routing and a login page as the functionality expands. To integrate routing, I have included the following code: app.module.ts // Importing various modules @NgModule({ declarations: [ App ...

How to upload a TypeScript package to NPM for installation using both import and CDN methods

I am eager to publish my TypeScript project on NPM. I am currently using the TypeScript Compiler (tsc) to transpile the .ts files of my project into output .js file(s). To generate the output files, I simply use the tsc command. Here is my tsconfig.json: ...