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);
}
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.