Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cf89a26

Browse filesBrowse files
SepandardJeanMeche
authored andcommitted
feat(http): add checker for required interceptor registration
Introduced a CHECKED_INTERCEPTORS token to track registered interceptors. Added logic in withInterceptors to register interceptor names and validate their presence. Added ensureInterceptorRegistered utility to enforce the registration of mandatory interceptors. update tests and document resolves #57409
1 parent 586be0e commit cf89a26
Copy full SHA for cf89a26

File tree

Expand file treeCollapse file tree

5 files changed

+97
-1
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+97
-1
lines changed

‎adev/src/content/guide/http/interceptors.md

Copy file name to clipboardExpand all lines: adev/src/content/guide/http/interceptors.md
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,43 @@ bootstrapApplication(AppComponent, {providers: [
194194
</docs-code>
195195

196196
DI-based interceptors run in the order that their providers are registered. In an app with an extensive and hierarchical DI configuration, this order can be very hard to predict.
197+
198+
199+
## Ensuring Required Interceptors Are Registered
200+
201+
Angular provides a mechanism to keep track of registered interceptors using a specialized injection token. This ensures that interceptors critical to application functionality are not omitted accidentally.
202+
203+
### The `CHECKED_INTERCEPTORS` Token
204+
205+
The `CHECKED_INTERCEPTORS` injection token is used to maintain a list of all interceptors registered in the application.
206+
207+
<docs-code language="ts">
208+
const CHECKED_INTERCEPTORS = new InjectionToken<Set<string>>(
209+
'CHECKED_INTERCEPTORS',
210+
{
211+
factory: () => new Set<string>(),
212+
}
213+
);
214+
</docs-code>
215+
216+
To register interceptors during application setup:
217+
218+
<docs-code language="ts">
219+
bootstrapApplication(AppComponent, {
220+
providers: [
221+
provideHttpClient(
222+
withInterceptors([loggingInterceptor, authInterceptor]),
223+
),
224+
],
225+
});
226+
</docs-code>
227+
228+
229+
To ensure a critical interceptor like `authInterceptor` is registered:
230+
231+
<docs-code language="ts">
232+
ensureInterceptorRegistered(authInterceptor);
233+
</docs-code>
234+
235+
This will throw an error if `authInterceptor` is not registered.
236+

‎packages/common/http/public_api.ts

Copy file name to clipboardExpand all lines: packages/common/http/public_api.ts
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export {
3838
withNoXsrfProtection,
3939
withRequestsMadeViaParent,
4040
withXsrfConfiguration,
41+
CHECKED_INTERCEPTORS,
42+
ensureInterceptorRegistered
4143
} from './src/provider';
4244
export {HttpRequest} from './src/request';
4345
export {

‎packages/common/http/src/errors.ts

Copy file name to clipboardExpand all lines: packages/common/http/src/errors.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ export const enum RuntimeErrorCode {
2525
JSONP_WRONG_RESPONSE_TYPE = 2811,
2626
JSONP_HEADERS_NOT_SUPPORTED = 2812,
2727
KEEPALIVE_NOT_SUPPORTED_WITH_XHR = 2813,
28+
MISSING_INTERCEPTOR = 2814,
2829
}

‎packages/common/http/src/provider.ts

Copy file name to clipboardExpand all lines: packages/common/http/src/provider.ts
+30Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
InjectionToken,
1313
makeEnvironmentProviders,
1414
Provider,
15+
ɵRuntimeError as RuntimeError
1516
} from '@angular/core';
1617

1718
import {HttpBackend, HttpHandler} from './backend';
@@ -38,6 +39,7 @@ import {
3839
XSRF_HEADER_NAME,
3940
xsrfInterceptorFn,
4041
} from './xsrf';
42+
import { RuntimeErrorCode } from './errors';
4143

4244
/**
4345
* Identifies a particular kind of `HttpFeature`.
@@ -74,6 +76,17 @@ function makeHttpFeature<KindT extends HttpFeatureKind>(
7476
};
7577
}
7678

79+
/**
80+
* Token to track registered interceptors.
81+
* @publicApi
82+
*/
83+
export const CHECKED_INTERCEPTORS = new InjectionToken<Set<HttpInterceptorFn>>(
84+
ngDevMode ? 'CHECKED_INTERCEPTORS' : '',
85+
{
86+
factory: () => new Set<HttpInterceptorFn>(), // Initialize an empty set for tracking.
87+
}
88+
);
89+
7790
/**
7891
* Configures Angular's `HttpClient` service to be available for injection.
7992
*
@@ -161,6 +174,8 @@ export function withInterceptors(
161174
return makeHttpFeature(
162175
HttpFeatureKind.Interceptors,
163176
interceptorFns.map((interceptorFn) => {
177+
const checkedInterceptors = inject(CHECKED_INTERCEPTORS);
178+
checkedInterceptors.add(interceptorFn);
164179
return {
165180
provide: HTTP_INTERCEPTOR_FNS,
166181
useValue: interceptorFn,
@@ -170,10 +185,25 @@ export function withInterceptors(
170185
);
171186
}
172187

188+
173189
const LEGACY_INTERCEPTOR_FN = new InjectionToken<HttpInterceptorFn>(
174190
ngDevMode ? 'LEGACY_INTERCEPTOR_FN' : '',
175191
);
176192

193+
/**
194+
* Ensures that a required interceptor is registered.
195+
* @publicApi
196+
*/
197+
export function ensureInterceptorRegistered(interceptor: HttpInterceptorFn): void {
198+
const checkedInterceptors = inject(CHECKED_INTERCEPTORS);
199+
if (!checkedInterceptors.has(interceptor)) {
200+
throw new RuntimeError(
201+
RuntimeErrorCode.MISSING_INTERCEPTOR,
202+
ngDevMode && `Required interceptor "${interceptor.name}" is not registered. Please add it using withInterceptors().`,
203+
);
204+
}
205+
}
206+
177207
/**
178208
* Includes class-based interceptors configured using a multi-provider in the current injector into
179209
* the configured `HttpClient` instance.

‎packages/common/http/test/provider_spec.ts

Copy file name to clipboardExpand all lines: packages/common/http/test/provider_spec.ts
+24-1Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import {
4747
withNoXsrfProtection,
4848
withRequestsMadeViaParent,
4949
withXsrfConfiguration,
50+
CHECKED_INTERCEPTORS,
51+
ensureInterceptorRegistered,
5052
} from '../src/provider';
5153

5254
describe('without provideHttpClientTesting', () => {
@@ -179,12 +181,33 @@ describe('provideHttpClient', () => {
179181
provideHttpClientTesting(),
180182
],
181183
});
182-
184+
185+
const checkedInterceptors = TestBed.inject(CHECKED_INTERCEPTORS);
186+
expect([...checkedInterceptors].some((fn) => fn.name === 'alpha')).toBe(true);
187+
expect([...checkedInterceptors].some((fn) => fn.name === 'beta')).toBe(true);
188+
183189
TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
184190
const req = TestBed.inject(HttpTestingController).expectOne('/test');
185191
expect(req.request.headers.get('X-Tag')).toEqual('alpha,beta');
186192
req.flush('');
187193
});
194+
195+
it('should throw error if interceptor is not registered', () => {
196+
TestBed.configureTestingModule({
197+
providers: [
198+
provideHttpClient(),
199+
provideHttpClientTesting(),
200+
],
201+
});
202+
203+
const missingInterceptorFn = makeLiteralTagInterceptorFn('missingInterceptor');
204+
205+
expect(() => {
206+
ensureInterceptorRegistered(missingInterceptorFn);
207+
}).toThrowError(
208+
'Required interceptor is not registered. Please add it using withInterceptors().'
209+
);
210+
188211

189212
it('should accept multiple separate interceptor configs', () => {
190213
TestBed.configureTestingModule({

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.