Home > Software engineering >  NullInjectorError: No provider for MatSelect! in angular 14
NullInjectorError: No provider for MatSelect! in angular 14

Time:07-15

I have updated angualar project from v8 to latest and mat-select throws the error.

Angular version : 14
Angular Material version : 14.0.4

ERROR NullInjectorError: R3InjectorError(AppModule)[MatSelect -> MatSelect -> MatSelect]: NullInjectorError: No provider for MatSelect!

My code in stackblitz: https://stackblitz.com/edit/angular-ivy-v987dv

module.ts

import { MatSelectModule } from '@angular/material/select';

imports: [
        MatSelectModule,
    ],

component.html

<mat-form-field appearance="outline">
    <mat-label>Select Category<sup >*</sup></mat-label>
    <mat-select formControlName="categories" [multiple]="true" (selectionChange)="elm.close()" #elm>
      <mat-select-trigger>Select Category<sup >*</sup></mat-select-trigger>
      <mat-select-search [(ngModel)]="categoriesFilter" [ngModelOptions]="{standalone: true}"
        [showAddButton]="false"></mat-select-search>
      <mat-optgroup
        *ngFor="let adOptionData of categoryList | filterSubArray:'adOptions':'name,mediaType':categoriesFilter"
        [label]="adOptionData.name">
        <mat-option
          *ngFor="let subCategory of adOptionData.adOptions | filter:'name,mediaType':categoriesFilter"
          [value]="subCategory">
          {{subCategory.name}}
        </mat-option>
      </mat-optgroup>
    </mat-select>
    <mat-error *ngIf="registerForm2.get('categories').touched">
      <span
        *ngIf="registerForm2.get('categories').errors && registerForm2.get('categories').errors.required">Categories
        are required</span>
    </mat-error>
</mat-form-field>

i think the error in mat-select-search. i used mat-select-search component and is code here

mat-select-search.module.ts

import { NgModule } from '@angular/core';
import { MatSelectSearchComponent } from './mat-select-search.component';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { CommonModule } from '@angular/common';
import { MatSelectModule } from '@angular/material/select';

@NgModule({
  imports: [
    CommonModule,
    MatButtonModule,    
    MatInputModule,
    MatSelectModule
  ],
  declarations: [
    MatSelectSearchComponent
  ],
  exports: [
    MatButtonModule,
    MatInputModule,
    MatSelectSearchComponent
  ]
})
export class MatSelectSearchModule { }

mat-select-search.component.html

input matInput />

<div 
     [ngClass]="{'mat-select-search-inner-multiple': matSelect.multiple}">
  <input matInput
         
         #searchSelectInput
         (keydown)="_handleKeydown($event)"
         (input)="onInputChange($event.target.value)"
         (blur)="onBlur($event.target.value)"
         [placeholder]="placeholderLabel"
  />
  <button mat-button *ngIf="value"
          mat-icon-button
          aria-label="Clear"
          (click)="_reset(true)"
          >
    <!-- <mat-icon>close</mat-icon> -->
    <!-- <i ></i> -->
    <svg style="width:24px;height:24px" viewBox="0 0 24 24">
      <path fill="currentColor" d="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z" />
  </svg>
  </button>
</div>
<div *ngIf="noEntriesFoundLabel && value && _options?.length === 0 "
     >
  {{noEntriesFoundLabel}} 
  <button mat-button mat-raised-button *ngIf="showAddButton" (click)="addButtonClick()">Add</button> 
</div>

mat-select-search.component.ts

import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Inject, Input, OnDestroy, OnInit, QueryList, ViewChild,Output
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatOption } from '@angular/material/core';
import { MatSelect } from '@angular/material-experimental/mdc-select';
import { Subject } from 'rxjs/Subject';
import { take, takeUntil } from 'rxjs/operators';
  
  @Component({
    selector: 'mat-select-search',
    templateUrl: './mat-select-search.component.html',
    styleUrls: ['./mat-select-search.component.scss'],
    providers: [
      {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => MatSelectSearchComponent),
        multi: true
      }
    ],
    changeDetection: ChangeDetectionStrategy.OnPush
  })

  export class MatSelectSearchComponent implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor {
  
    /** Label of the search placeholder */
    @Input() placeholderLabel = 'Search'; 
    /** Add Event Output */
    @Output() addButton = new EventEmitter<any>();
    /** Label to be shown when no entries are found. Set to null if no message should be shown. */
    @Input() noEntriesFoundLabel = 'No search found';
    /** Reference to the search input field */
    @ViewChild('searchSelectInput', { read: ElementRef, static: true }) searchSelectInput: ElementRef;
    @Input() showAddButton:boolean=false;
    /** Current search value */
    get value(): string {
      return this._value;
    }
    private _value: string;
    onChange: Function = (_: any) => {};
    onTouched: Function = (_: any) => {};
    /** Reference to the MatSelect options */
    public _options: QueryList<MatOption>;
    /** Previously selected values when using <mat-select [multiple]="true">*/
    private previousSelectedValues: any[];
    /** Whether the backdrop class has been set */
    private overlayClassSet = false;
    /** Event that emits when the current value changes */
    private change = new EventEmitter<string>();
    /** Subject that emits when the component has been destroyed. */
    private _onDestroy = new Subject<void>();
  
    constructor(@Inject(MatSelect) public matSelect: MatSelect, private changeDetectorRef: ChangeDetectorRef) {}
  
    ngOnInit() {
      // set custom panel class
      const panelClass = 'mat-select-search-panel';
      if (this.matSelect.panelClass) {
        if (Array.isArray(this.matSelect.panelClass)) {
          this.matSelect.panelClass.push(panelClass);
        } else if (typeof this.matSelect.panelClass === 'string') {
          this.matSelect.panelClass = [this.matSelect.panelClass, panelClass];
        } else if (typeof this.matSelect.panelClass === 'object') {
          this.matSelect.panelClass[panelClass] = true;
        }
      } else {
        this.matSelect.panelClass = panelClass;
      }
      // when the select dropdown panel is opened or closed
      this.matSelect.openedChange
        .pipe(takeUntil(this._onDestroy))
        .subscribe((opened) => {
          if (opened) {
            // focus the search field when opening
            this._focus();
          } else {
            // clear it when closing
            this._reset();
          }
        });
      // set the first item active after the options changed
      this.matSelect.openedChange
        .pipe(take(1))
        .pipe(takeUntil(this._onDestroy))
        .subscribe(() => {
          this._options = this.matSelect.options;
          this._options.changes
            .pipe(takeUntil(this._onDestroy))
            .subscribe(() => {
              const keyManager = this.matSelect._keyManager;
              if (keyManager && this.matSelect.panelOpen) {
                // avoid "expression has been changed" error
                setTimeout(() => {
                  keyManager.setFirstItemActive();
                });
              }
            });
        });
      // detect changes when the input changes
      this.change
        .pipe(takeUntil(this._onDestroy))
        .subscribe(() => {
          this.changeDetectorRef.detectChanges();
        });
      this.initMultipleHandling();
    }
  
    ngOnDestroy() {
      this._onDestroy.next();
      this._onDestroy.complete();
    }
  
    ngAfterViewInit() {
      this.setOverlayClass();
    }
    /**
     * Handles the key down event with MatSelect.
     * Allows e.g. selecting with enter key, navigation with arrow keys, etc.
     * @param {KeyboardEvent} event
     * @private
     */
    _handleKeydown(event: KeyboardEvent) {
      if (event.keyCode === 32) {
        // do not propagate spaces to MatSelect, as this would select the currently active option
        event.stopPropagation();
      }
    }
    writeValue(value: string) {
      const valueChanged = value !== this._value;
      if (valueChanged) {
        this._value = value;
        this.change.emit(value);
      }
    }
    onInputChange(value) {
      const valueChanged = value !== this._value;
      if (valueChanged) {
        this._value = value;
        this.onChange(value);
        this.change.emit(value);
      }
    }
    onBlur(value: string) {
      this.writeValue(value);
      this.onTouched();
    }
    registerOnChange(fn: Function) {
      this.onChange = fn;
    }
    registerOnTouched(fn: Function) {
      this.onTouched = fn;
    }
    /**
     * Focuses the search input field
     * @private
     */
    public _focus() {
      if (!this.searchSelectInput) {
        return;
      }
      // save and restore scrollTop of panel, since it will be reset by focus()
      // note: this is hacky
      const panel = this.matSelect.panel.nativeElement;
      const scrollTop = panel.scrollTop;
      // focus
      this.searchSelectInput.nativeElement.focus();
      panel.scrollTop = scrollTop;
    }
    /**
     * Resets the current search value
     * @param {boolean} focus whether to focus after resetting
     * @private
     */
    public _reset(focus?: boolean) {
      if (!this.searchSelectInput) {
        return;
      }
      this.searchSelectInput.nativeElement.value = '';
      this.onInputChange('');
      if (focus) {
        this._focus();
      }
    }
    /**
     * Sets the overlay class  to correct offsetY
     * so that the selected option is at the position of the select box when opening
     */
    private setOverlayClass() {
      if (this.overlayClassSet) {
        return;
      }
      const overlayClass = 'cdk-overlay-pane-select-search';
      this.overlayClassSet = true;
    }
    private initMultipleHandling() {
      this.matSelect.valueChange
        .pipe(takeUntil(this._onDestroy))
        .subscribe((values) => {
          if (this.matSelect.multiple) {
            let restoreSelectedValues = false;
            if (this._value && this._value.length
              && this.previousSelectedValues && Array.isArray(this.previousSelectedValues)) {
              if (!values || !Array.isArray(values)) {
                values = [];
              }
              const optionValues = this.matSelect.options.map(option => option.value);
              this.previousSelectedValues.forEach(previousValue => {
                if (values.indexOf(previousValue) === -1 && optionValues.indexOf(previousValue) === -1) {
                  values.push(previousValue);
                  restoreSelectedValues = true;
                }
              });
            }
            if (restoreSelectedValues) {
              this.matSelect._onChange(values);
            }
            this.previousSelectedValues = values;
          }
        });
    }
  
    addButtonClick(){
      this.addButton.emit(this._value);
    }
  }

CodePudding user response:

You need to add the MatSelectModule to the app module.

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    MatSelectModule // <- this needs to be added
  ]
})
export class NoNameNeeded { }

updated answer:

I have gone for the injector.get() instead of defining the matSelect inside the injector. This works!

before:

import { MatSelect } from '@angular/material-experimental/mdc-select';

After:

import { MatSelect } from '@angular/material/select';

Before:

  constructor(
    @Inject(MatSelect) public matSelect: MatSelect,
    private changeDetectorRef: ChangeDetectorRef
  ) {}

After:

  matSelect = null;

  constructor(
    private injector: Injector,
    private changeDetectorRef: ChangeDetectorRef
  ) {
    this.matSelect = this.injector.get(MatSelect);
  }

stackblitz

CodePudding user response:

This line

import { MatSelect } from '@angular/material-experimental/mdc-select';

inside your mat-select-search.component.ts is the culprit.

It should be

import { MatSelect } from '@angular/material/select';

This isn't very easy to notice and it's definitely not the greatest idea to have 2 modules exactly named the same within your (in this case: Google's) library.

  • Related