Useful or not, from you.
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

That's a useful answer
Without any help

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.