Skip to content

Commit 4bed062

Browse files
JeanMechecrisbeto
authored andcommitted
feat(http): Provide http services in root (#56212)
The changes introduced in this commit allows to use the HttpClient without the provider function. PR Close #56212
1 parent be71103 commit 4bed062

File tree

10 files changed

+213
-150
lines changed

10 files changed

+213
-150
lines changed

goldens/public-api/common/http/index.api.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export const HTTP_TRANSFER_CACHE_ORIGIN_MAP: InjectionToken<Record<string, strin
3838
export abstract class HttpBackend implements HttpHandler {
3939
// (undocumented)
4040
abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
41+
// (undocumented)
42+
static ɵfac: i0.ɵɵFactoryDeclaration<HttpBackend, never>;
43+
// (undocumented)
44+
static ɵprov: i0.ɵɵInjectableDeclaration<HttpBackend>;
4145
}
4246

4347
// @public
@@ -2562,6 +2566,10 @@ export enum HttpFeatureKind {
25622566
export abstract class HttpHandler {
25632567
// (undocumented)
25642568
abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
2569+
// (undocumented)
2570+
static ɵfac: i0.ɵɵFactoryDeclaration<HttpHandler, never>;
2571+
// (undocumented)
2572+
static ɵprov: i0.ɵɵInjectableDeclaration<HttpHandler>;
25652573
}
25662574

25672575
// @public
@@ -3153,6 +3161,10 @@ export class HttpXhrBackend implements HttpBackend {
31533161
// @public
31543162
export abstract class HttpXsrfTokenExtractor {
31553163
abstract getToken(): string | null;
3164+
// (undocumented)
3165+
static ɵfac: i0.ɵɵFactoryDeclaration<HttpXsrfTokenExtractor, never>;
3166+
// (undocumented)
3167+
static ɵprov: i0.ɵɵInjectableDeclaration<HttpXsrfTokenExtractor>;
31563168
}
31573169

31583170
// @public

packages/common/http/public_api.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
export {HttpBackend, HttpHandler} from './src/backend';
9+
export {
10+
HttpBackend,
11+
HttpHandler,
12+
// The following private symbols isn't used outside this package but has a usage in G3.
13+
HttpInterceptorHandler as ɵHttpInterceptingHandler,
14+
} from './src/backend';
1015
export {HttpClient} from './src/client';
1116
export {HttpContext, HttpContextToken} from './src/context';
1217
export {FetchBackend} from './src/fetch';
@@ -16,8 +21,6 @@ export {
1621
HttpHandlerFn,
1722
HttpInterceptor,
1823
HttpInterceptorFn,
19-
HttpInterceptorHandler as ɵHttpInterceptorHandler,
20-
HttpInterceptorHandler as ɵHttpInterceptingHandler,
2124
} from './src/interceptor';
2225
export {JsonpClientBackend, JsonpInterceptor} from './src/jsonp';
2326
export {HttpClientJsonpModule, HttpClientModule, HttpClientXsrfModule} from './src/module';

packages/common/http/src/backend.ts

Lines changed: 121 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,142 @@ import {Observable} from 'rxjs';
1010

1111
import {HttpRequest} from './request';
1212
import {HttpEvent} from './response';
13+
import {FetchBackend} from './fetch';
14+
import {HttpXhrBackend} from './xhr';
15+
import {isPlatformServer} from '@angular/common';
16+
import {
17+
EnvironmentInjector,
18+
inject,
19+
Injectable,
20+
PLATFORM_ID,
21+
ɵConsole as Console,
22+
ɵformatRuntimeError as formatRuntimeError,
23+
PendingTasks,
24+
} from '@angular/core';
25+
import {finalize} from 'rxjs/operators';
26+
27+
import {RuntimeErrorCode} from './errors';
28+
import {
29+
ChainedInterceptorFn,
30+
HTTP_INTERCEPTOR_FNS,
31+
HTTP_ROOT_INTERCEPTOR_FNS,
32+
REQUESTS_CONTRIBUTE_TO_STABILITY,
33+
chainedInterceptorFn,
34+
interceptorChainEndFn,
35+
} from './interceptor';
1336

1437
/**
15-
* Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a
16-
* `HttpResponse`.
38+
* A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend.
1739
*
18-
* `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the
19-
* first interceptor in the chain, which dispatches to the second, etc, eventually reaching the
20-
* `HttpBackend`.
40+
* Interceptors sit between the `HttpClient` interface and the `HttpBackend`.
2141
*
22-
* In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain.
42+
* When injected, `HttpBackend` dispatches requests directly to the backend, without going
43+
* through the interceptor chain.
2344
*
2445
* @publicApi
2546
*/
26-
export abstract class HttpHandler {
47+
@Injectable({providedIn: 'root', useExisting: HttpXhrBackend})
48+
export abstract class HttpBackend implements HttpHandler {
2749
abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
2850
}
2951

52+
let fetchBackendWarningDisplayed = false;
53+
54+
/** Internal function to reset the flag in tests */
55+
export function resetFetchBackendWarningFlag() {
56+
fetchBackendWarningDisplayed = false;
57+
}
58+
59+
@Injectable({providedIn: 'root'})
60+
export class HttpInterceptorHandler implements HttpHandler {
61+
private chain: ChainedInterceptorFn<unknown> | null = null;
62+
private readonly pendingTasks = inject(PendingTasks);
63+
private readonly contributeToStability = inject(REQUESTS_CONTRIBUTE_TO_STABILITY);
64+
65+
constructor(
66+
private backend: HttpBackend,
67+
private injector: EnvironmentInjector,
68+
) {
69+
// We strongly recommend using fetch backend for HTTP calls when SSR is used
70+
// for an application. The logic below checks if that's the case and produces
71+
// a warning otherwise.
72+
if ((typeof ngDevMode === 'undefined' || ngDevMode) && !fetchBackendWarningDisplayed) {
73+
// This flag is necessary because provideHttpClientTesting() overrides the backend
74+
// even if `withFetch()` is used within the test. When the testing HTTP backend is provided,
75+
// no HTTP calls are actually performed during the test, so producing a warning would be
76+
// misleading.
77+
const isTestingBackend = (this.backend as any).isTestingBackend;
78+
79+
if (
80+
typeof ngServerMode !== 'undefined' &&
81+
ngServerMode &&
82+
!(this.backend instanceof FetchBackend) &&
83+
!isTestingBackend
84+
) {
85+
fetchBackendWarningDisplayed = true;
86+
injector
87+
.get(Console)
88+
.warn(
89+
formatRuntimeError(
90+
RuntimeErrorCode.NOT_USING_FETCH_BACKEND_IN_SSR,
91+
'Angular detected that `HttpClient` is not configured ' +
92+
"to use `fetch` APIs. It's strongly recommended to " +
93+
'enable `fetch` for applications that use Server-Side Rendering ' +
94+
'for better performance and compatibility. ' +
95+
'To enable `fetch`, add the `withFetch()` to the `provideHttpClient()` ' +
96+
'call at the root of the application.',
97+
),
98+
);
99+
}
100+
}
101+
}
102+
103+
handle(initialRequest: HttpRequest<any>): Observable<HttpEvent<any>> {
104+
if (this.chain === null) {
105+
const dedupedInterceptorFns = Array.from(
106+
new Set([
107+
...this.injector.get(HTTP_INTERCEPTOR_FNS),
108+
...this.injector.get(HTTP_ROOT_INTERCEPTOR_FNS, []),
109+
]),
110+
);
111+
112+
// Note: interceptors are wrapped right-to-left so that final execution order is
113+
// left-to-right. That is, if `dedupedInterceptorFns` is the array `[a, b, c]`, we want to
114+
// produce a chain that is conceptually `c(b(a(end)))`, which we build from the inside
115+
// out.
116+
this.chain = dedupedInterceptorFns.reduceRight(
117+
(nextSequencedFn, interceptorFn) =>
118+
chainedInterceptorFn(nextSequencedFn, interceptorFn, this.injector),
119+
interceptorChainEndFn as ChainedInterceptorFn<unknown>,
120+
);
121+
}
122+
123+
if (this.contributeToStability) {
124+
const removeTask = this.pendingTasks.add();
125+
return this.chain(initialRequest, (downstreamRequest) =>
126+
this.backend.handle(downstreamRequest),
127+
).pipe(finalize(removeTask));
128+
} else {
129+
return this.chain(initialRequest, (downstreamRequest) =>
130+
this.backend.handle(downstreamRequest),
131+
);
132+
}
133+
}
134+
}
135+
30136
/**
31-
* A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend.
137+
* Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a
138+
* `HttpResponse`.
32139
*
33-
* Interceptors sit between the `HttpClient` interface and the `HttpBackend`.
140+
* `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the
141+
* first interceptor in the chain, which dispatches to the second, etc, eventually reaching the
142+
* `HttpBackend`.
34143
*
35-
* When injected, `HttpBackend` dispatches requests directly to the backend, without going
36-
* through the interceptor chain.
144+
* In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain.
37145
*
38146
* @publicApi
39147
*/
40-
export abstract class HttpBackend implements HttpHandler {
148+
@Injectable({providedIn: 'root', useExisting: HttpInterceptorHandler})
149+
export abstract class HttpHandler {
41150
abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
42151
}

packages/common/http/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ function addBody<T>(
127127
*
128128
* @publicApi
129129
*/
130-
@Injectable()
130+
@Injectable({providedIn: 'root'})
131131
export class HttpClient {
132132
constructor(private handler: HttpHandler) {}
133133

packages/common/http/src/fetch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
import {Observable, Observer} from 'rxjs';
1818
import {RuntimeErrorCode} from './errors';
1919

20-
import {HttpBackend} from './backend';
20+
import type {HttpBackend} from './backend';
2121
import {HttpHeaders} from './headers';
2222
import {
2323
ACCEPT_HEADER,

packages/common/http/src/interceptor.ts

Lines changed: 7 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,15 @@
99
import {
1010
EnvironmentInjector,
1111
inject,
12-
Injectable,
1312
InjectionToken,
1413
runInInjectionContext,
15-
ɵConsole as Console,
16-
ɵformatRuntimeError as formatRuntimeError,
1714
PendingTasks,
1815
} from '@angular/core';
1916
import {Observable} from 'rxjs';
2017
import {finalize} from 'rxjs/operators';
2118

22-
import {HttpBackend, HttpHandler} from './backend';
23-
import {RuntimeErrorCode} from './errors';
24-
import {FetchBackend} from './fetch';
19+
import type {HttpHandler} from './backend';
20+
2521
import {HttpRequest} from './request';
2622
import {HttpEvent} from './response';
2723

@@ -144,12 +140,12 @@ export type HttpInterceptorFn = (
144140
* `HttpInterceptorFn`, which is a useful abstraction for including different kinds of interceptors
145141
* (e.g. legacy class-based interceptors) in the same chain.
146142
*/
147-
type ChainedInterceptorFn<RequestT> = (
143+
export type ChainedInterceptorFn<RequestT> = (
148144
req: HttpRequest<RequestT>,
149145
finalHandlerFn: HttpHandlerFn,
150146
) => Observable<HttpEvent<RequestT>>;
151147

152-
function interceptorChainEndFn(
148+
export function interceptorChainEndFn(
153149
req: HttpRequest<any>,
154150
finalHandlerFn: HttpHandlerFn,
155151
): Observable<HttpEvent<any>> {
@@ -160,7 +156,7 @@ function interceptorChainEndFn(
160156
* Constructs a `ChainedInterceptorFn` which adapts a legacy `HttpInterceptor` to the
161157
* `ChainedInterceptorFn` interface.
162158
*/
163-
function adaptLegacyInterceptorToChain(
159+
export function adaptLegacyInterceptorToChain(
164160
chainTailFn: ChainedInterceptorFn<any>,
165161
interceptor: HttpInterceptor,
166162
): ChainedInterceptorFn<any> {
@@ -174,7 +170,7 @@ function adaptLegacyInterceptorToChain(
174170
* Constructs a `ChainedInterceptorFn` which wraps and invokes a functional interceptor in the given
175171
* injector.
176172
*/
177-
function chainedInterceptorFn(
173+
export function chainedInterceptorFn(
178174
chainTailFn: ChainedInterceptorFn<unknown>,
179175
interceptorFn: HttpInterceptorFn,
180176
injector: EnvironmentInjector,
@@ -202,6 +198,7 @@ export const HTTP_INTERCEPTORS = new InjectionToken<readonly HttpInterceptor[]>(
202198
*/
203199
export const HTTP_INTERCEPTOR_FNS = new InjectionToken<readonly HttpInterceptorFn[]>(
204200
ngDevMode ? 'HTTP_INTERCEPTOR_FNS' : '',
201+
{factory: () => []},
205202
);
206203

207204
/**
@@ -249,89 +246,3 @@ export function legacyInterceptorFnFactory(): HttpInterceptorFn {
249246
}
250247
};
251248
}
252-
253-
let fetchBackendWarningDisplayed = false;
254-
255-
/** Internal function to reset the flag in tests */
256-
export function resetFetchBackendWarningFlag() {
257-
fetchBackendWarningDisplayed = false;
258-
}
259-
260-
@Injectable()
261-
export class HttpInterceptorHandler extends HttpHandler {
262-
private chain: ChainedInterceptorFn<unknown> | null = null;
263-
private readonly pendingTasks = inject(PendingTasks);
264-
private readonly contributeToStability = inject(REQUESTS_CONTRIBUTE_TO_STABILITY);
265-
266-
constructor(
267-
private backend: HttpBackend,
268-
private injector: EnvironmentInjector,
269-
) {
270-
super();
271-
272-
// We strongly recommend using fetch backend for HTTP calls when SSR is used
273-
// for an application. The logic below checks if that's the case and produces
274-
// a warning otherwise.
275-
if ((typeof ngDevMode === 'undefined' || ngDevMode) && !fetchBackendWarningDisplayed) {
276-
// This flag is necessary because provideHttpClientTesting() overrides the backend
277-
// even if `withFetch()` is used within the test. When the testing HTTP backend is provided,
278-
// no HTTP calls are actually performed during the test, so producing a warning would be
279-
// misleading.
280-
const isTestingBackend = (this.backend as any).isTestingBackend;
281-
282-
if (
283-
typeof ngServerMode !== 'undefined' &&
284-
ngServerMode &&
285-
!(this.backend instanceof FetchBackend) &&
286-
!isTestingBackend
287-
) {
288-
fetchBackendWarningDisplayed = true;
289-
injector
290-
.get(Console)
291-
.warn(
292-
formatRuntimeError(
293-
RuntimeErrorCode.NOT_USING_FETCH_BACKEND_IN_SSR,
294-
'Angular detected that `HttpClient` is not configured ' +
295-
"to use `fetch` APIs. It's strongly recommended to " +
296-
'enable `fetch` for applications that use Server-Side Rendering ' +
297-
'for better performance and compatibility. ' +
298-
'To enable `fetch`, add the `withFetch()` to the `provideHttpClient()` ' +
299-
'call at the root of the application.',
300-
),
301-
);
302-
}
303-
}
304-
}
305-
306-
override handle(initialRequest: HttpRequest<any>): Observable<HttpEvent<any>> {
307-
if (this.chain === null) {
308-
const dedupedInterceptorFns = Array.from(
309-
new Set([
310-
...this.injector.get(HTTP_INTERCEPTOR_FNS),
311-
...this.injector.get(HTTP_ROOT_INTERCEPTOR_FNS, []),
312-
]),
313-
);
314-
315-
// Note: interceptors are wrapped right-to-left so that final execution order is
316-
// left-to-right. That is, if `dedupedInterceptorFns` is the array `[a, b, c]`, we want to
317-
// produce a chain that is conceptually `c(b(a(end)))`, which we build from the inside
318-
// out.
319-
this.chain = dedupedInterceptorFns.reduceRight(
320-
(nextSequencedFn, interceptorFn) =>
321-
chainedInterceptorFn(nextSequencedFn, interceptorFn, this.injector),
322-
interceptorChainEndFn as ChainedInterceptorFn<unknown>,
323-
);
324-
}
325-
326-
if (this.contributeToStability) {
327-
const removeTask = this.pendingTasks.add();
328-
return this.chain(initialRequest, (downstreamRequest) =>
329-
this.backend.handle(downstreamRequest),
330-
).pipe(finalize(removeTask));
331-
} else {
332-
return this.chain(initialRequest, (downstreamRequest) =>
333-
this.backend.handle(downstreamRequest),
334-
);
335-
}
336-
}
337-
}

0 commit comments

Comments
 (0)