TypeError: Cannot read properties of undefined (reading 'ngModule')

7.3k views Asked by At

Runing test I get this error TypeError: Cannot read properties of undefined (reading 'ngModule')

I dont exactly know what is causing this error, but google said circular dependency issue but I am not sure where to start. Using NX-monorepo with angular and jest for testing Even my tests related to components are failing with the same error, thanks!

module.spec.ts

import { StatusOverviewModule } from './status-overview.module';

describe('StatusOverviewModule', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [StatusOverviewModule],
    });
  });

  it('initializes', () => {
    const module = TestBed.inject(StatusOverviewModule);
    expect(module).toBeTruthy();
  });
});

Status-overview.module

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { TRANSLOCO_SCOPE } from '@ngneat/transloco';
import { SohoComponentsModule } from 'ids-enterprise-ng';

import {
  HttpAuthInterceptor,
  IonDeskIntegrationModule,
  ModuleAccessibilityGuard,
  PermissionResource,
} from '@core/ion-desk-integration';
import { scopeLoader, TranslationsModule, TranslationsResolver } from '@core/translations';
import { TilesModule } from '@shared/tiles';

import { StatusOverviewHomeComponent } from './components/status-overview-home/status-overview-home.component';
import { StatusTileCircleComponent } from './components/status-tile-circle/status-tile-circle.component';
import { LOCAL_REST_URL, REST_ENDPOINT } from './constants/status-overview.constants';
import { HttpErrorInterceptor } from './interceptors/http-error.interceptor';
import { LongNumberConversion } from './pipe/long-conversion.pipe';
import { StatusDataService } from './services/status-data.service';
import { StatusOverviewComponent } from './status-overview.component';

export const statusOverviewRoutes: Routes = [
  {
    path: '',
    component: StatusOverviewComponent,
    canActivate: [ModuleAccessibilityGuard],
    data: {
      permissionResource: PermissionResource.ANY,
    },
    resolve: {
      translations: TranslationsResolver,
    },
  },
];

@NgModule({
  imports: [
    CommonModule,
    SohoComponentsModule,
    TranslationsModule,
    TilesModule,
    RouterModule.forChild(statusOverviewRoutes),
    FormsModule,
    HttpClientModule,
    IonDeskIntegrationModule.forFeature({
      devApiPath: LOCAL_REST_URL + '/' + REST_ENDPOINT,
      serverApiPath: REST_ENDPOINT,
    }),
  ],
  providers: [
    {
      provide: TRANSLOCO_SCOPE,
      useValue: {
        scope: 'statusOverview',
        loader: scopeLoader((lang, root) => import(`../assets/${root}/${lang}.json`)),
      },
    },
    { provide: HTTP_INTERCEPTORS, 
      useClass: HttpErrorInterceptor,
       multi: true },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpAuthInterceptor,
      multi: true,
    },
    StatusDataService,
  ],
  declarations: [StatusOverviewComponent, StatusOverviewHomeComponent, StatusTileCircleComponent, LongNumberConversion],
})
export class StatusOverviewModule {}
3

There are 3 answers

3
Juan Vicente Berzosa Tejero On

I guess you missed to wait for the module creation. Try like this:

   await TestBed.configureTestingModule
1
user2732627 On

Since this is the first post that comes up in the google results, for this error, going to answer it here.

After searching for an answer in google, and then eventually just debugging I found out that this error means that one of your Modules that is being imported somewhere does contain an undefined and not a Module. This could be due to the barrel-imports, but the easiest way is just remove the breaking Module from the imports.

To find out in what module you have that undefined module being imported, you'd probably have to modify one of the source files from @angular/core package. The name of it, is core.mjs or something similar. Find compileNgModuleDefs function and modify the getter to print the name of the module if it contains undefined import:

    Object.defineProperty(moduleType, NG_MOD_DEF, {
        configurable: true,
        get: () => {
            if (ngModuleDef === null) {
                if (ngDevMode && ngModule.imports && ngModule.imports.indexOf(moduleType) > -1) {
                    // We need to assert this immediately, because allowing it to continue will cause it to
                    // go into an infinite loop before we've reached the point where we throw all the errors.
                    throw new Error(`'${stringifyForError(moduleType)}' module can't import itself`);
                }
                const compiler = getCompilerFacade({ usage: 0 /* Decorator */, kind: 'NgModule', type: moduleType });
                // these three lines where added
                if(ngModule.imports && ngModule.imports.some(i => i === undefined)) {
                    console.log(moduleType.name)
                }
                ngModuleDef = compiler.compileNgModule(angularCoreEnv, `ng:///${moduleType.name}/ɵmod.js`, {
                    type: moduleType,
                    bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(resolveForwardRef),
                    declarations: declarations.map(resolveForwardRef),
                    imports: flatten(ngModule.imports || EMPTY_ARRAY)
                        .map(resolveForwardRef)
                        .map(expandModuleWithProviders),
                    exports: flatten(ngModule.exports || EMPTY_ARRAY)
                        .map(resolveForwardRef)
                        .map(expandModuleWithProviders),
                    schemas: ngModule.schemas ? flatten(ngModule.schemas) : null,
                    id: ngModule.id || null,
                });
                // Set `schemas` on ngModuleDef to an empty array in JIT mode to indicate that runtime
                // should verify that there are no unknown elements in a template. In AOT mode, that check
                // happens at compile time and `schemas` information is not present on Component and Module
                // defs after compilation (so the check doesn't happen the second time at runtime).
                if (!ngModuleDef.schemas) {
                    ngModuleDef.schemas = [];
                }
            }
            return ngModuleDef;
        }
    });

This function might be different depending which version of angular you are using. For me it is @angular/[email protected]

0
Laszlo Sarvold On

I think user2732627 should be the accepted answer, thanks to his solution I was able to debug and fix the issue. However I want to share some additional information in case it helps.

I was also getting same error. Expanding it a bit the output:

TypeError: Cannot read properties of undefined (reading 'ngModule')
    at isModuleWithProviders (webpack:///node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:27329:18 <- test/unit_index.js:126452:16)
    at expandModuleWithProviders (webpack:///node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:27322:9 <- test/unit_index.js:126444:7)
    at Array.map (<anonymous>)
    at Function.get (webpack:///node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:26961:26 <- test/unit_index.js:126025:84)

So the output there indicates which file of the node_modules the error happened, and so to console.log what we need. The first line we read, core.js:27329, is too specific and inside a function called for every module. Same with seccond line, core.js:27322:. By following the code in the core.js referenced for the lines output we reach the getter as mentioned in the solution by user2732627.

In my case, after following the error traces, it happened to be a module with an undefined object in the exports array, so I added few lines above core.js:26961 (you can see it in the fourth line of the error traces). So I added same console.log as in the main solution but checking for both, empty object in the exports and imports. Looks something like this:

/**
 * Compiles and adds the `ɵmod`, `ɵfac` and `ɵinj` properties to the module class.
 *
 * It's possible to compile a module via this API which will allow duplicate declarations in its
 * root.
 */
function compileNgModuleDefs(moduleType, ngModule, allowDuplicateDeclarationsInRoot = false) {
    ngDevMode && assertDefined(moduleType, 'Required value moduleType');
    ngDevMode && assertDefined(ngModule, 'Required value ngModule');
    const declarations = flatten(ngModule.declarations || EMPTY_ARRAY);
    let ngModuleDef = null;
    Object.defineProperty(moduleType, NG_MOD_DEF, {
        configurable: true,
        get: () => {
            if (ngModuleDef === null) {
                if (ngDevMode && ngModule.imports && ngModule.imports.indexOf(moduleType) > -1) {
                    // We need to assert this immediately, because allowing it to continue will cause it to
                    // go into an infinite loop before we've reached the point where we throw all the errors.
                    throw new Error(`'${stringifyForError(moduleType)}' module can't import itself`);
                }
                const compiler = getCompilerFacade({ usage: 0 /* Decorator */, kind: 'NgModule', type: moduleType });
                // these three lines where added
                if(ngModule.imports && ngModule.imports.some(i => i === undefined)) {
                    console.log('module with undefined imports')
                    console.log(moduleType.name)
                }
                // these three lines where added
                if(ngModule.exports && ngModule.exports.some(i => i === undefined)) {
                    console.log('module with undefined exports')
                    console.log(moduleType.name)
                }
                ngModuleDef = compiler.compileNgModule(angularCoreEnv, `ng:///${moduleType.name}/ɵmod.js`, {
                    type: moduleType,
                    bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(resolveForwardRef),
                    declarations: declarations.map(resolveForwardRef),
                    imports: flatten(ngModule.imports || EMPTY_ARRAY)
                        .map(resolveForwardRef)
                        .map(expandModuleWithProviders),
                    exports: flatten(ngModule.exports || EMPTY_ARRAY)
                        .map(resolveForwardRef)
                        .map(expandModuleWithProviders), // <-- this is where the error was thrown, line 26961 of core.js

Finally logged the module with an undefined element in the exports array, which in my case was caused by a service defined like this:

@Injectable({
    providedIn: FooModule, // <-- actually not exported in FooModule
})

Fixed it by providing it in 'root' instead. Could have added it to the exports array in that module as well to fix the issue.