provider.ts
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EnvironmentProviders, inject, InjectionToken, makeEnvironmentProviders, Provider} from '@angular/core';
import {HttpBackend, HttpHandler} from './backend';
import {HttpClient} from './client';
import {FetchBackend} from './fetch';
import {HTTP_INTERCEPTOR_FNS, HttpInterceptorFn, HttpInterceptorHandler, legacyInterceptorFnFactory, PRIMARY_HTTP_BACKEND} from './interceptor';
import {jsonpCallbackContext, JsonpCallbackContext, JsonpClientBackend, jsonpInterceptorFn} from './jsonp';
import {HttpXhrBackend} from './xhr';
import {HttpXsrfCookieExtractor, HttpXsrfTokenExtractor, XSRF_COOKIE_NAME, XSRF_ENABLED, XSRF_HEADER_NAME, xsrfInterceptorFn} from './xsrf';
/**
* Identifies a particular kind of `HttpFeature`.
*
* @publicApi
*/
export enum HttpFeatureKind {
Interceptors,
LegacyInterceptors,
CustomXsrfConfiguration,
NoXsrfProtection,
JsonpSupport,
RequestsMadeViaParent,
Fetch,
}
/**
* A feature for use when configuring `provideHttpClient`.
*
* @publicApi
*/
export interface HttpFeature<KindT extends HttpFeatureKind> {
ɵkind: KindT;
ɵproviders: Provider[];
}
function makeHttpFeature<KindT extends HttpFeatureKind>(
kind: KindT, providers: Provider[]): HttpFeature<KindT> {
return {
ɵkind: kind,
ɵproviders: providers,
};
}
/**
* Configures Angular's `HttpClient` service to be available for injection.
*
* By default, `HttpClient` will be configured for injection with its default options for XSRF
* protection of outgoing requests. Additional configuration options can be provided by passing
* feature functions to `provideHttpClient`. For example, HTTP interceptors can be added using the
* `withInterceptors(...)` feature.
*
* <div class="alert is-helpful">
*
* It's strongly recommended to enable
* [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) for applications that use
* Server-Side Rendering for better performance and compatibility. To enable `fetch`, add
* `withFetch()` feature to the `provideHttpClient()` call at the root of the application:
*
* ```
* provideHttpClient(withFetch());
* ```
*
* </div>
*
* @see {@link withInterceptors}
* @see {@link withInterceptorsFromDi}
* @see {@link withXsrfConfiguration}
* @see {@link withNoXsrfProtection}
* @see {@link withJsonpSupport}
* @see {@link withRequestsMadeViaParent}
* @see {@link withFetch}
*/
export function provideHttpClient(...features: HttpFeature<HttpFeatureKind>[]):
EnvironmentProviders {
if (ngDevMode) {
const featureKinds = new Set(features.map(f => f.ɵkind));
if (featureKinds.has(HttpFeatureKind.NoXsrfProtection) &&
featureKinds.has(HttpFeatureKind.CustomXsrfConfiguration)) {
throw new Error(
ngDevMode ?
`Configuration error: found both withXsrfConfiguration() and withNoXsrfProtection() in the same call to provideHttpClient(), which is a contradiction.` :
'');
}
}
const providers: Provider[] = [
HttpClient,
HttpXhrBackend,
HttpInterceptorHandler,
{provide: HttpHandler, useExisting: HttpInterceptorHandler},
{provide: HttpBackend, useExisting: HttpXhrBackend},
{
provide: HTTP_INTERCEPTOR_FNS,
useValue: xsrfInterceptorFn,
multi: true,
},
{provide: XSRF_ENABLED, useValue: true},
{provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor},
];
for (const feature of features) {
providers.push(...feature.ɵproviders);
}
return makeEnvironmentProviders(providers);
}
/**
* Adds one or more functional-style HTTP interceptors to the configuration of the `HttpClient`
* instance.
*
* @see {@link HttpInterceptorFn}
* @see {@link provideHttpClient}
* @publicApi
*/
export function withInterceptors(interceptorFns: HttpInterceptorFn[]):
HttpFeature<HttpFeatureKind.Interceptors> {
return makeHttpFeature(HttpFeatureKind.Interceptors, interceptorFns.map(interceptorFn => {
return {
provide: HTTP_INTERCEPTOR_FNS,
useValue: interceptorFn,
multi: true,
};
}));
}
const LEGACY_INTERCEPTOR_FN = new InjectionToken<HttpInterceptorFn>('LEGACY_INTERCEPTOR_FN');
/**
* Includes class-based interceptors configured using a multi-provider in the current injector into
* the configured `HttpClient` instance.
*
* Prefer `withInterceptors` and functional interceptors instead, as support for DI-provided
* interceptors may be phased out in a later release.
*
* @see {@link HttpInterceptor}
* @see {@link HTTP_INTERCEPTORS}
* @see {@link provideHttpClient}
*/
export function withInterceptorsFromDi(): HttpFeature<HttpFeatureKind.LegacyInterceptors> {
// Note: the legacy interceptor function is provided here via an intermediate token
// (`LEGACY_INTERCEPTOR_FN`), using a pattern which guarantees that if these providers are
// included multiple times, all of the multi-provider entries will have the same instance of the
// interceptor function. That way, the `HttpINterceptorHandler` will dedup them and legacy
// interceptors will not run multiple times.
return makeHttpFeature(HttpFeatureKind.LegacyInterceptors, [
{
provide: LEGACY_INTERCEPTOR_FN,
useFactory: legacyInterceptorFnFactory,
},
{
provide: HTTP_INTERCEPTOR_FNS,
useExisting: LEGACY_INTERCEPTOR_FN,
multi: true,
}
]);
}
/**
* Customizes the XSRF protection for the configuration of the current `HttpClient` instance.
*
* This feature is incompatible with the `withNoXsrfProtection` feature.
*
* @see {@link provideHttpClient}
*/
export function withXsrfConfiguration(
{cookieName, headerName}: {cookieName?: string, headerName?: string}):
HttpFeature<HttpFeatureKind.CustomXsrfConfiguration> {
const providers: Provider[] = [];
if (cookieName !== undefined) {
providers.push({provide: XSRF_COOKIE_NAME, useValue: cookieName});
}
if (headerName !== undefined) {
providers.push({provide: XSRF_HEADER_NAME, useValue: headerName});
}
return makeHttpFeature(HttpFeatureKind.CustomXsrfConfiguration, providers);
}
/**
* Disables XSRF protection in the configuration of the current `HttpClient` instance.
*
* This feature is incompatible with the `withXsrfConfiguration` feature.
*
* @see {@link provideHttpClient}
*/
export function withNoXsrfProtection(): HttpFeature<HttpFeatureKind.NoXsrfProtection> {
return makeHttpFeature(HttpFeatureKind.NoXsrfProtection, [
{
provide: XSRF_ENABLED,
useValue: false,
},
]);
}
/**
* Add JSONP support to the configuration of the current `HttpClient` instance.
*
* @see {@link provideHttpClient}
*/
export function withJsonpSupport(): HttpFeature<HttpFeatureKind.JsonpSupport> {
return makeHttpFeature(HttpFeatureKind.JsonpSupport, [
JsonpClientBackend,
{provide: JsonpCallbackContext, useFactory: jsonpCallbackContext},
{provide: HTTP_INTERCEPTOR_FNS, useValue: jsonpInterceptorFn, multi: true},
]);
}
/**
* Configures the current `HttpClient` instance to make requests via the parent injector's
* `HttpClient` instead of directly.
*
* By default, `provideHttpClient` configures `HttpClient` in its injector to be an independent
* instance. For example, even if `HttpClient` is configured in the parent injector with
* one or more interceptors, they will not intercept requests made via this instance.
*
* With this option enabled, once the request has passed through the current injector's
* interceptors, it will be delegated to the parent injector's `HttpClient` chain instead of
* dispatched directly, and interceptors in the parent configuration will be applied to the request.
*
* If there are several `HttpClient` instances in the injector hierarchy, it's possible for
* `withRequestsMadeViaParent` to be used at multiple levels, which will cause the request to
* "bubble up" until either reaching the root level or an `HttpClient` which was not configured with
* this option.
*
* @see {@link provideHttpClient}
* @developerPreview
*/
export function withRequestsMadeViaParent(): HttpFeature<HttpFeatureKind.RequestsMadeViaParent> {
return makeHttpFeature(HttpFeatureKind.RequestsMadeViaParent, [
{
provide: HttpBackend,
useFactory: () => {
const handlerFromParent = inject(HttpHandler, {skipSelf: true, optional: true});
if (ngDevMode && handlerFromParent === null) {
throw new Error(
'withRequestsMadeViaParent() can only be used when the parent injector also configures HttpClient');
}
return handlerFromParent;
},
},
]);
}
/**
* Configures the current `HttpClient` instance to make requests using the fetch API.
*
* This `FetchBackend` requires the support of the Fetch API which is available on all evergreen
* browsers and on NodeJS from v18 onward.
*
* Note: The Fetch API doesn't support progress report on uploads.
*
* @publicApi
*/
export function withFetch(): HttpFeature<HttpFeatureKind.Fetch> {
if ((typeof ngDevMode === 'undefined' || ngDevMode) && typeof fetch !== 'function') {
// TODO: Create a runtime error
// TODO: Use ENVIRONMENT_INITIALIZER to contextualize the error message (browser or server)
throw new Error(
'The `withFetch` feature of HttpClient requires the `fetch` API to be available. ' +
'If you run the code in a Node environment, make sure you use Node v18.10 or later.');
}
return makeHttpFeature(HttpFeatureKind.Fetch, [
FetchBackend,
{provide: HttpBackend, useExisting: FetchBackend},
{provide: PRIMARY_HTTP_BACKEND, useExisting: FetchBackend},
]);
}