diff --git a/apps/demos/configs/Angular/config.js b/apps/demos/configs/Angular/config.js index b0f9dcd50207..23094919eb34 100644 --- a/apps/demos/configs/Angular/config.js +++ b/apps/demos/configs/Angular/config.js @@ -176,6 +176,7 @@ window.config = { /* devextreme-angular umd maps */ 'devextreme-angular': 'bundles:devextreme-angular/devextreme-angular.umd.js', 'devextreme-angular/core': 'bundles:devextreme-angular/devextreme-angular-core.umd.js', + 'devextreme-angular/http': 'bundles:devextreme-angular/devextreme-angular-http.umd.js', ...componentNames.reduce((acc, name) => { acc[`devextreme-angular/ui/${name}`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}.umd.js`; return acc; diff --git a/apps/demos/rollup.devextreme-angular.umd.config.mjs b/apps/demos/rollup.devextreme-angular.umd.config.mjs index 57a33d607012..c251517ea56a 100644 --- a/apps/demos/rollup.devextreme-angular.umd.config.mjs +++ b/apps/demos/rollup.devextreme-angular.umd.config.mjs @@ -10,13 +10,14 @@ const componentNames = fs.readdirSync(baseDir) .map((fileName) => fileName.replace('.mjs.map', '')); const inputs = { - 'devextreme-angular-core': `${baseDir}devextreme-angular-core.mjs`, - 'devextreme-angular': `${baseDir}devextreme-angular.mjs`, - ...componentNames.reduce((acc, name) => { - acc[name] = `${baseDir}${name}.mjs`; + 'devextreme-angular': `${baseDir}devextreme-angular.mjs`, + 'devextreme-angular-core': `${baseDir}devextreme-angular-core.mjs`, + 'devextreme-angular-http': `${baseDir}devextreme-angular-http.mjs`, + ...componentNames.reduce((acc, name) => { + acc[name] = `${baseDir}${name}.mjs`; - return acc; - }, {}), + return acc; + }, {}), }; const getLibName = (file) => file diff --git a/packages/devextreme-angular/src/http/index.ts b/packages/devextreme-angular/src/http/index.ts index b78f528bc2cc..90b6b7088293 100644 --- a/packages/devextreme-angular/src/http/index.ts +++ b/packages/devextreme-angular/src/http/index.ts @@ -1,16 +1,23 @@ -import { NgModule } from '@angular/core'; +import { NgModule, Injector, createNgModuleRef } from '@angular/core'; import { HttpClient, HttpClientModule } from '@angular/common/http'; import devextremeAjax from 'devextreme/core/utils/ajax'; -// eslint-disable-next-line import/named import { sendRequestFactory } from './ajax'; @NgModule({ exports: [], - imports: [HttpClientModule], + imports: [], providers: [], }) export class DxHttpModule { - constructor(httpClient: HttpClient) { + constructor(injector: Injector) { + let httpClient: HttpClient = injector.get(HttpClient, null); + + if (!httpClient) { + const moduleRef = createNgModuleRef(HttpClientModule, injector); + + httpClient = moduleRef.injector.get(HttpClient); + } + devextremeAjax.inject({ sendRequest: sendRequestFactory(httpClient) }); } } diff --git a/packages/devextreme-angular/tests/src/http/ajax-bootstrap-interceptors.spec.ts b/packages/devextreme-angular/tests/src/http/ajax-bootstrap-interceptors.spec.ts new file mode 100644 index 000000000000..4d042e5cfc74 --- /dev/null +++ b/packages/devextreme-angular/tests/src/http/ajax-bootstrap-interceptors.spec.ts @@ -0,0 +1,106 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { + provideHttpClient, + withInterceptors, + HttpInterceptorFn, + HttpClient, +} from '@angular/common/http'; +import { ApplicationRef, Component } from '@angular/core'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { DxHttpModule } from 'devextreme-angular/http'; +import DataSource from 'devextreme/data/data_source'; +import ODataStore from 'devextreme/data/odata/store'; +import { throwError } from 'rxjs'; + +const TEST_URL = ''; +const interceptors: Record void> = {}; + +interceptors.interceptorFn = () => {}; + +const testInterceptorFn: HttpInterceptorFn = () => { + interceptors.interceptorFn(); + return throwError(() => ({ + status: 403, + statusText: 'Request intercepted. Access Denied', + })); +}; + +@Component({ + standalone: true, + selector: 'test-app', + imports: [DxHttpModule], + template: '---', +}) +class TestAppComponent { + constructor(private readonly httpClient: HttpClient) {} + + fetchData() { + return this.httpClient.get(TEST_URL).toPromise(); + } + + loadDataSource() { + const dataSource = new DataSource({ + store: new ODataStore({ + version: 2, + url: TEST_URL, + }), + }); + + return dataSource.load() as Promise; + } +} + +describe('Using DxHttpModule in application with interceptors provided in bootstrapApplication() ', () => { + let httpTestingControllerMock: HttpTestingController; + let component: TestAppComponent; + + beforeEach(async () => { + const testApp = document.createElement('test-app'); + + document.body.appendChild(testApp); + + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + const appRef = await bootstrapApplication(TestAppComponent, { + providers: [ + provideHttpClient(withInterceptors([testInterceptorFn])), + { provide: HttpClientTestingModule }, + ], + }); + + const applicationRef = appRef.injector.get(ApplicationRef); + + component = applicationRef.components[0].instance as TestAppComponent; + + httpTestingControllerMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpTestingControllerMock?.verify(); + }); + + it('should call interceptors while calling httpClient directly ', (done) => { + const interceptorFnSpy = spyOn(interceptors, 'interceptorFn'); + + component + .fetchData() + .catch(() => { + expect(interceptorFnSpy).toHaveBeenCalledTimes(1); + done(); + }).finally(() => {}); + }); + + it('dataSource load() should be intercepted', (done) => { + const interceptorFnSpy = spyOn(interceptors, 'interceptorFn'); + + // eslint-disable-next-line no-void + void component.loadDataSource() + .catch(() => { + expect(interceptorFnSpy).toHaveBeenCalledTimes(1); + done(); + }); + }); +});