Rando Shtishi
Rando Shtishi

Reputation: 1522

Interceptor declared in app.module is not intercepting call from one lazy loaded module

I have created an interceptor to add the authentication token and refresh the authentication token if it expires like below:

@Injectable()
export class CustomInterceptors implements HttpInterceptor {

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
             // ... implementation details
    }

}

I have registered this interceptor in the main module of angular (app.module) like below:

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        BrowserAnimationsModule,
        RouterModule.forRoot(appRoutes),
        HttpClientModule, 
        MatSnackBarModule,

        // Fuse modules
        FuseModule.forRoot(fuseConfig),
        FuseProgressBarModule,
        FuseSidebarModule,
        FuseThemeOptionsModule,

        // App modules
        LayoutModule,
        LightboxModule
      
    ],
    bootstrap: [
        AppComponent
    ],
    providers: [AuthGuard, UsersService, ErrorService, {
        provide: HTTP_INTERCEPTORS,
        useClass: CustomInterceptors,
        multi: true
    }]
})
export class AppModule {
}

I have two lazy loaded modules AModule and BModule like below:

-- AModule
const routes: Routes = [
    {
        path: '**',
        component: AComponent,
        resolve: {
            interventions: AService
        }
    }
];

@NgModule({
    declarations: [
        AComponent
    ],
    imports: [
        RouterModule.forChild(routes),
        MatButtonModule,
        MatCheckboxModule,
        MatDatepickerModule,
        MatFormFieldModule,
        MatIconModule,
        MatInputModule,
        MatMenuModule,
        MatRippleModule,
        MatTableModule,
        MatToolbarModule,
        MatSelectModule,
        MatSnackBarModule,
        MatSortModule,
        MatTooltipModule,
        TranslateModule.forChild(),
        MatProgressSpinnerModule,
        MatPaginatorModule,
        FuseSharedModule,
        FuseConfirmDialogModule,
        FuseSidebarModule,
        FuseDeleteInfoModule
    ],
    providers: [
        AsService
    ]
})
export class AModule {
}


--------------------------------------------------------------------


--BModule
const routes: Routes = [
     {
        path: '**',
        component: BComponent,
        resolve: {
            contacts: BService
        }
    },
];

@NgModule({
    declarations: [
        BComponent
    ],
    imports: [
        RouterModule.forChild(routes),
        MatButtonModule,
        MatCheckboxModule,
        MatExpansionModule,
        MatDatepickerModule,
        MatFormFieldModule,
        MatIconModule,
        MatInputModule,
        MatMenuModule,
        MatRippleModule,
        MatTableModule,
        MatToolbarModule,
        MatProgressSpinnerModule,
        MatAutocompleteModule,
        FuseSharedModule,
        MatTabsModule,
        FuseConfirmDialogModule,
        FuseSidebarModule,
        MatSelectModule,
        MatSnackBarModule,
        MatChipsModule,
        MatTableExporterModule,
        MatSortModule,
        MatTooltipModule,
        TranslateModule.forChild(),
        MatPaginatorModule,
        MatRadioModule,
        MatListModule,
        MatExpansionModule,
        MatSidenavModule,
        MatCardModule,
        ReactiveFormsModule,
        FormsModule,    
        GalleryModule,
        LightboxModule,
        
    ],
    providers: [
        BService,  
        // {
        //     provide: HTTP_INTERCEPTORS,
        //     useClass: CustomInterceptors,
        //     multi: true
        // }
    ]
})
export class BModule {
}

The Routing for the two lazy loading modules is like below:

   {
        path: 'A',
        loadChildren: './a/a.module#AModule',
        canActivate: [AuthGuard], 
        data: { roles: ['Administrator', 'Supervisor','Operator']}
    },
    {
        path: 'B',
        loadChildren: './b/b.module#BModule',
        canActivate: [AuthGuard], 
        data: { roles: ['Administrator']}
    }

The problem is that the interceptor is not working for calls made from the BModule but it works successfully for the AModule.

I have found a workaround to this, to add the interceptor in the providers section of the BModule. This isn't right because I am basically instantiating another object of the interceptor in BModule. I want to have the interceptor declared in the app.module to be shared among all lazy loaded modules.

I will appreciate any help or guide to a solution to this problem.

Regards, Rando.

P.S

Another lazy load module(Lets called C module) that was working, broke down like the B module when someone committed some changes. I ran a git diff to see the differences that made the module stop working. The result is like below:

 const routes: Routes = [
@@ -134,7 +138,10 @@ const routes: Routes = [
         MatCardModule,
         ReactiveFormsModule,
         MatListModule,
-        FormsModule
+        FormsModule,
+        GalleryModule,
+        LightboxModule,
+        ImageGalleryModule
     ],
     providers: [
        CService,

I made some tests, I was able to identify that interceptor will stop working for the module that imports ImageGalleryModule. The following is the ImageGalleryModule:

@NgModule({
    declarations: [
        ImageGalleryComponent
    ],
    imports: [
        MatButtonModule,
        MatCheckboxModule,
        MatDatepickerModule,
        MatFormFieldModule,
        MatIconModule,
        MatInputModule,
        MatMenuModule,
        MatRippleModule,
        MatTableModule,
        MatToolbarModule,
        MatTableExporterModule,
        MatSnackBarModule,
        MatSortModule,
        TranslateModule.forChild(),
        MatPaginatorModule,
        MatTooltipModule,
        MatProgressSpinnerModule,
        FuseSharedModule,
        FuseConfirmDialogModule,
        FuseSidebarModule,
        MatSelectModule,
        MatRadioModule,
        MatListModule,
        MatExpansionModule,
        MatSidenavModule,
        MatChipsModule,
        MatTabsModule,
        MatCardModule,
        MatTooltipModule,
        MatAutocompleteModule,
        FormsModule,
        ReactiveFormsModule,
        GalleryModule,
        LightboxModule,

    ],
    providers: [
        DatePipe,
    ],
    exports:[
        ImageGalleryComponent
    ]
})
export class ImageGalleryModule {
}

This is strange that importing this module causes the interceptor to not work since this module does not Import or Export the HttpClientModule.

Upvotes: 2

Views: 2384

Answers (1)

Rando Shtishi
Rando Shtishi

Reputation: 1522

This is a workaround and not a solution to the problem. So this will not be marked as an accepted answer.

I will start by describing the problem:

HTTP_INTERCEPTOR are reset when lazy-loaded module import HttpClientModule. All interceptors are set up to work on a single instance of the HttpClient service. The moment that the lazy-loaded module imports HttpClientModule, it creates a new instance of HttpClient service. This new instance is injected in all components of the lazy loaded module and every call made from this instance is not intercepted by the interceptor declared in the app-module.

In my case, I searched all around the project and didn't find any import of HttpClientModule except the app module. After hours of debugging, I found out that BModule and CModule imported ImageGalleryModule that was the culprit for these behaviours. After investigating in ImageGalleryModule, I found out that the modules GalleryModule, LightboxModule imported the HttpClientModule.

When lazy-loaded module imports a module that imports the HttpClientModule, the interceptors are no longer available because a new instance of HttpClient is created for that lazy-loaded module.

WorkAround:

I refactored all my services provided in the app module in a separate module called CoreModule like below:

@NgModule({
    imports:[

    ],
    declarations: [
    ],
    providers: [
    ]
})

export class CoreModule {

    static forRoot(): ModuleWithProviders {
        return {
          ngModule: CoreModule,
          providers: [
            AuthGuard, Service A, Service B, Service C, 
            [{ provide: HTTP_INTERCEPTORS, useClass: CustomInterceptors, multi: true }]
          ],
        };
      }
}

After that imported CoreModule in app-module and in the lazy-loaded modules like below:

@NgModule({
    declarations: [
...
    ],
    imports: [
...
    CoreModule.forRoot(),
...

I don't consider this as a solution because we have created a separate interceptor for the lazy-loaded module that import GalleryModule. We have three instances of the interceptor:

  • The interceptor instantiated in app-module and AModule
  • The interceptor instantiated in BModule
  • The interceptor instantiated in CModule

Upvotes: 1

Related Questions