angular: Error: No provider for InjectionToken mat-autocomplete-scroll-strategy

I upgraded my project from Angular 8 to 9 and refactored my code to take advantage of the ability to lazy load components. For most of my components this works great, but I have one that is throwing an error and I need help resolving the issue.

Here is my component code followed by the error. The module is defined in the same file as the component.

import { Component, OnInit, OnDestroy, NgModule } from '@angular/core';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { switchMap, debounceTime, tap, filter } from 'rxjs/operators';
import { MatFormFieldModule, MatInputModule, MatAutocompleteModule, MatButtonModule, MatProgressSpinnerModule, MatIconModule, MatDialogModule } from '@angular/material';
 
import { SearchService } from './search.service';
import { SuggestResult } from './suggest-result';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from 'src/app/shared/shared.module';
 
@Component({
    selector: 'app-search',
    templateUrl: './search.component.html',
    styleUrls: ['./search.component.scss'],
    providers: [ FormBuilder]
})
export class SearchComponent implements OnInit, OnDestroy {
 
//code removed
 
}
 
@NgModule({
    imports: [
        CommonModule,
        HttpClientModule,
        FormsModule,
        ReactiveFormsModule,
        MatFormFieldModule,
        MatInputModule,
        MatAutocompleteModule,
        MatButtonModule,
        MatProgressSpinnerModule,
        MatIconModule,
        SharedModule,
        MatDialogModule
    ],
    declarations: [SearchComponent]
})
export class SearchModule { }
<div>
    <div class="form-container">
        <form [formGroup]='resultsForm'>
            <mat-form-field [class.mat-form-field-invalid]="!formFieldValid">
                <input matInput placeholder="Search term" type="search" [matAutocomplete]="SearchAuto" formControlName='SearchUserInput'>
            </mat-form-field>

            <div *ngIf="!formFieldValid" class="error-msg"> {{ errorMessage }} </div>
            
            <mat-autocomplete #SearchAuto="matAutocomplete" [displayWith]="getSelectedText"
                (optionSelected)='onItemSelected($event.option.value)'>
                <mat-option *ngIf="isLoading" class="is-loading">
                    <mat-spinner mode="indeterminate" diameter="25"></mat-spinner>
                </mat-option>

                <ng-container *ngIf="!isLoading">
                    <mat-option *ngFor="let result of filteredResults" [value]="result">
                        <span>{{ result.name }}</span>
                    </mat-option>
                </ng-container>
            </mat-autocomplete>
        </form>
        <mat-icon>search</mat-icon>
        <button mat-button *ngIf="isNotEmpty()" matSuffix mat-icon-button aria-label="Clear" (click)="clearSearch()">
            <mat-icon>close</mat-icon>
        </button>
    </div>
</div>

The error:

null: Error: Uncaught (in promise): NullInjectorError: R3InjectorError(AppModule)[InjectionToken mat-autocomplete-scroll-strategy -> InjectionToken mat-autocomplete-scroll-strategy -> InjectionToken mat-autocomplete-scroll-strategy]: NullInjectorError: No provider for InjectionToken mat-autocomplete-scroll-strategy! ./node_modules/@angular/core/ivy_ngcc/fesm5/core.js/NullInjector.prototype.get@http://localhost:4200/vendor.js:37066:25 ./node_modules/@angular/core/ivy_ngcc/fesm5/core.js/R3Injector.prototype.get@http://localhost:4200/vendor.js:47422:33 ./node_modules/@angular/core/ivy_ngcc/fesm5/core.js/R3Injector.prototype.get@http://localhost:4200/vendor.js:47422:33 ./node_modules/@angular/core/ivy_ngcc/fesm5/core.js/R3Injector.prototype.get@http://localhost:4200/vendor.js:47422:33 ./node_modules/@angular/core/ivy_ngcc/fesm5/core.js/NgModuleRef$1.prototype.get@http://localhost:4200/vendor.js:60382:33 get@http://localhost:4200/vendor.js:58703:35 getOrCreateInjectable@http://localhost:4200/vendor.js:39763:39 ɵɵdirectiveInject@http://localhost:4200/vendor.js:50290:12 MatAutocompleteTrigger_Factory@http://localhost:4200/vendor.js:76965:788 getNodeInjectable@http://localhost:4200/vendor.js:39871:44 searchTokensOnInjector@http://localhost:4200/vendor.js:39807:16 getOrCreateInjectable@http://localhost:4200/vendor.js:39729:58 ɵɵdirectiveInject@http://localhost:4200/vendor.js:50290:12 ɵɵinject@http://localhost:4200/vendor.js:36956:57 factory@http://localhost:4200/vendor.js:47681:44 multiResolve@http://localhost:4200/vendor.js:55407:21 multiProvidersFactoryResolver@http://localhost:4200/vendor.js:55372:12 getNodeInjectable@http://localhost:4200/vendor.js:39871:44 searchTokensOnInjector@http://localhost:4200/vendor.js:39807:16 getOrCreateInjectable@http://localhost:4200/vendor.js:39729:58 ɵɵdirectiveInject@http://localhost:4200/vendor.js:50290:12 FormControlName_Factory@http://localhost:4200/vendor.js:74579:400 getNodeInjectable@http://localhost:4200/vendor.js:39871:44 searchTokensOnInjector@http://localhost:4200/vendor.js:39807:16 getOrCreateInjectable@http://localhost:4200/vendor.js:39729:58 ɵɵdirectiveInject@http://localhost:4200/vendor.js:50290:12 ɵɵinject@http://localhost:4200/vendor.js:36956:57 factory@http://localhost:4200/vendor.js:47681:44 getNodeInjectable@http://localhost:4200/vendor.js:39871:44 searchTokensOnInjector@http://localhost:4200/vendor.js:39807:16 getOrCreateInjectable@http://localhost:4200/vendor.js:39729:58 ɵɵdirectiveInject@http://localhost:4200/vendor.js:50290:12 MatInput_Factory@http://localhost:4200/vendor.js:99837:370 getNodeInjectable@http://localhost:4200/vendor.js:39871:44 instantiateAllDirectives@http://localhost:4200/vendor.js:44362:42 createDirectivesInstances@http://localhost:4200/vendor.js:43753:29 ɵɵelementStart@http://localhost:4200/vendor.js:50436:34 ɵɵelement@http://localhost:4200/vendor.js:50487:19 SearchComponent_Template@http://localhost:4200/search-search-search-component.js:190:56 executeTemplate@http://localhost:4200/vendor.js:43726:19 renderView@http://localhost:4200/vendor.js:43551:28 renderComponent@http://localhost:4200/vendor.js:44741:15 renderChildComponents@http://localhost:4200/vendor.js:43412:24 renderView@http://localhost:4200/vendor.js:43576:34 ./node_modules/@angular/core/ivy_ngcc/fesm5/core.js/ComponentFactory.prototype.create@http://localhost:4200/vendor.js:58804:23 createContainerRef/R3ViewContainerRef</ViewContainerRef.prototype.createComponent@http://localhost:4200/vendor.js:46481:53 ./src/app/dynamic-loader/dynamic-loader.service.ts/DynamicLoaderService.prototype.addComponentToContainer/obs<@http://localhost:4200/main.js:583:23 ./node_modules/zone.js/dist/zone.js/</Zone$1</ZoneDelegate</ZoneDelegate.prototype.invoke@http://localhost:4200/polyfills.js:2740:30 onInvoke@http://localhost:4200/vendor.js:63629:33 ./node_modules/zone.js/dist/zone.js/</Zone$1</ZoneDelegate</ZoneDelegate.prototype.invoke@http://localhost:4200/polyfills.js:2739:36 ./node_modules/zone.js/dist/zone.js/</Zone$1</Zone</Zone.prototype.run@http://localhost:4200/polyfills.js:2497:47 scheduleResolveOrReject/<@http://localhost:4200/polyfills.js:3238:38 ./node_modules/zone.js/dist/zone.js/</Zone$1</ZoneDelegate</ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.js:2775:35 onInvokeTask@http://localhost:4200/vendor.js:63617:33 ./node_modules/zone.js/dist/zone.js/</Zone$1</ZoneDelegate</ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.js:2774:40 ./node_modules/zone.js/dist/zone.js/</Zone$1</Zone</Zone.prototype.runTask@http://localhost:4200/polyfills.js:2542:51 drainMicroTaskQueue@http://localhost:4200/polyfills.js:2955:39

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 17
  • Comments: 25 (6 by maintainers)

Most upvoted comments

It does not look like there’s a bug here, but there is a catch and ergonomic issue to this situation.

The root of the problem is that the SearchModule that imports MatAutocompleteModule is never instantiated, as it’s not imported from AppModule. This results in none of the (transitively) provided providers being available.

So as a solution, we need to ensure that the SearchModule is used when creating the SearchComponent. To achieve this, you’d currently need to inject the Compiler and use its compileModuleSync method to obtain an NgModuleFactory for the imported SearchModule class.

Importing the compiler seems counter-intuitive, as we don’t need JIT compilation here. Fortunately in Ivy, this comes at no additional costs: there is no need to use platformBrowserDynamic to pull in the whole compiler. This approach is actually exactly what the Router uses under the covers for its loadChildren support.

Given the NgModuleFactory, we can use its componentFactoryResolver to be able to create the component factory for SearchComponent in the context of the SearchModule instead of AppModule. Therefore, the injected ComponentFactoryResolver is no longer needed, as that is bound to AppModule.

import { Component, VERSION, ComponentFactoryResolver, ViewContainerRef, ViewChild, NgModuleRef, Compiler } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  
  constructor(private compiler: Compiler) {}

  @ViewChild("container", { read: ViewContainerRef })
  container: ViewContainerRef;

  loadsearch() {
    import("./search/search.component").then(c => {
      console.log(c);

      const ngModuleFactory = this.compiler.compileModuleSync(c.SearchModule);
      const ngModule = ngModuleFactory.create(this.container.injector);

      const factory = ngModule.componentFactoryResolver.resolveComponentFactory(c.SearchComponent);

      console.log(factory);

      this.container.createComponent(factory);
    });
  }
}

The ergonomics are not ideal here, as all APIs have been designed around the “factory” concept that was needed with ViewEngine (the renderer before Ivy).

I’m keeping this issue open for tracking this use-case, but I’m adjusting the severity label as you mentioned you started to take advantage of lazy loading which introduced the issue.

EDIT: Please bear in mind that the example above will create a new instance of SearchModule each time, which may not be desirable. Consider caching the ngModule somewhere (or even just factory in this case) but keep in mind that this instance is specific to a parent Injector, which is this.container.injector in this case.

I am also having this exact issue when having a lazy loaded module with MatAutocomplete

The fix was the same as before, to import MatTooltipModule in the App-level module, and not inside a sub-component. Is there a way to avoid importing it into the AppModule but to the lazy loaded modules where needed?

I was struggling with the same issue. providing MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY_PROVIDER and MAT_SELECT_SCROLL_STRATEGY_PROVIDER in the component level solved it for me.

providers: [
  MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY_PROVIDER,
  MAT_SELECT_SCROLL_STRATEGY_PROVIDER,
],

MatTooltipModule also throws ERROR Error: NullInjectorError: No provider for InjectionToken mat-tooltip-scroll-strategy!

The fix was the same as before, to import MatTooltipModule in the App-level module, and not inside a sub-component. The isolated case in StackBlitz works fine though https://stackblitz.com/edit/angular-nw6rvl

@schinks if you’ve got troubles using stackblitz, you could try https://ng-run.com/. GitHub repo is more problematic (takes us more time to understand what is going on) but would be OK if you can’t reproduce it otherwise.

In any case please try to keep the reproduce scenario to the absolute minimum…