angular/packages/platform-browser/src/browser.ts
Thomas Nguyen aaa00b3f9c refactor(core): Add global event delegation provider (#56247)
This replaces all addEventListener calls with a stashing function,
and installs an event listener on the document body to retrieve
the stashed function;

PR Close #56247
2024-06-27 14:24:47 +00:00

304 lines
9.9 KiB
TypeScript

/**
* @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 {
CommonModule,
DOCUMENT,
XhrFactory,
ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID,
} from '@angular/common';
import {
APP_ID,
ApplicationConfig as ApplicationConfigFromCore,
ApplicationModule,
ApplicationRef,
createPlatformFactory,
ErrorHandler,
Inject,
InjectionToken,
ModuleWithProviders,
NgModule,
NgZone,
Optional,
PLATFORM_ID,
PLATFORM_INITIALIZER,
platformCore,
PlatformRef,
Provider,
RendererFactory2,
SkipSelf,
StaticProvider,
Testability,
TestabilityRegistry,
Type,
ɵINJECTOR_SCOPE as INJECTOR_SCOPE,
ɵinternalCreateApplication as internalCreateApplication,
ɵRuntimeError as RuntimeError,
ɵsetDocument,
ɵTESTABILITY as TESTABILITY,
ɵTESTABILITY_GETTER as TESTABILITY_GETTER,
} from '@angular/core';
import {BrowserDomAdapter} from './browser/browser_adapter';
import {BrowserGetTestability} from './browser/testability';
import {BrowserXhr} from './browser/xhr';
import {DomRendererFactory2} from './dom/dom_renderer';
import {DomEventsPlugin} from './dom/events/dom_events';
import {EventDelegationPlugin} from './dom/events/event_delegation';
import {EVENT_MANAGER_PLUGINS, EventManager} from './dom/events/event_manager';
import {KeyEventsPlugin} from './dom/events/key_events';
import {SharedStylesHost} from './dom/shared_styles_host';
import {RuntimeErrorCode} from './errors';
/**
* Set of config options available during the application bootstrap operation.
*
* @publicApi
*
* @deprecated
* `ApplicationConfig` has moved, please import `ApplicationConfig` from `@angular/core` instead.
*/
// The below is a workaround to add a deprecated message.
type ApplicationConfig = ApplicationConfigFromCore;
export {ApplicationConfig};
/**
* Bootstraps an instance of an Angular application and renders a standalone component as the
* application's root component. More information about standalone components can be found in [this
* guide](guide/components/importing).
*
* @usageNotes
* The root component passed into this function *must* be a standalone one (should have the
* `standalone: true` flag in the `@Component` decorator config).
*
* ```typescript
* @Component({
* standalone: true,
* template: 'Hello world!'
* })
* class RootComponent {}
*
* const appRef: ApplicationRef = await bootstrapApplication(RootComponent);
* ```
*
* You can add the list of providers that should be available in the application injector by
* specifying the `providers` field in an object passed as the second argument:
*
* ```typescript
* await bootstrapApplication(RootComponent, {
* providers: [
* {provide: BACKEND_URL, useValue: 'https://yourdomain.com/api'}
* ]
* });
* ```
*
* The `importProvidersFrom` helper method can be used to collect all providers from any
* existing NgModule (and transitively from all NgModules that it imports):
*
* ```typescript
* await bootstrapApplication(RootComponent, {
* providers: [
* importProvidersFrom(SomeNgModule)
* ]
* });
* ```
*
* Note: the `bootstrapApplication` method doesn't include [Testability](api/core/Testability) by
* default. You can add [Testability](api/core/Testability) by getting the list of necessary
* providers using `provideProtractorTestingSupport()` function and adding them into the `providers`
* array, for example:
*
* ```typescript
* import {provideProtractorTestingSupport} from '@angular/platform-browser';
*
* await bootstrapApplication(RootComponent, {providers: [provideProtractorTestingSupport()]});
* ```
*
* @param rootComponent A reference to a standalone component that should be rendered.
* @param options Extra configuration for the bootstrap operation, see `ApplicationConfig` for
* additional info.
* @returns A promise that returns an `ApplicationRef` instance once resolved.
*
* @publicApi
*/
export function bootstrapApplication(
rootComponent: Type<unknown>,
options?: ApplicationConfig,
): Promise<ApplicationRef> {
return internalCreateApplication({rootComponent, ...createProvidersConfig(options)});
}
/**
* Create an instance of an Angular application without bootstrapping any components. This is useful
* for the situation where one wants to decouple application environment creation (a platform and
* associated injectors) from rendering components on a screen. Components can be subsequently
* bootstrapped on the returned `ApplicationRef`.
*
* @param options Extra configuration for the application environment, see `ApplicationConfig` for
* additional info.
* @returns A promise that returns an `ApplicationRef` instance once resolved.
*
* @publicApi
*/
export function createApplication(options?: ApplicationConfig) {
return internalCreateApplication(createProvidersConfig(options));
}
function createProvidersConfig(options?: ApplicationConfig) {
return {
appProviders: [...BROWSER_MODULE_PROVIDERS, ...(options?.providers ?? [])],
platformProviders: INTERNAL_BROWSER_PLATFORM_PROVIDERS,
};
}
/**
* Returns a set of providers required to setup [Testability](api/core/Testability) for an
* application bootstrapped using the `bootstrapApplication` function. The set of providers is
* needed to support testing an application with Protractor (which relies on the Testability APIs
* to be present).
*
* @returns An array of providers required to setup Testability for an application and make it
* available for testing using Protractor.
*
* @publicApi
*/
export function provideProtractorTestingSupport(): Provider[] {
// Return a copy to prevent changes to the original array in case any in-place
// alterations are performed to the `provideProtractorTestingSupport` call results in app
// code.
return [...TESTABILITY_PROVIDERS];
}
export function initDomAdapter() {
BrowserDomAdapter.makeCurrent();
}
export function errorHandler(): ErrorHandler {
return new ErrorHandler();
}
export function _document(): any {
// Tell ivy about the global document
ɵsetDocument(document);
return document;
}
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] = [
{provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
{provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true},
{provide: DOCUMENT, useFactory: _document, deps: []},
];
/**
* A factory function that returns a `PlatformRef` instance associated with browser service
* providers.
*
* @publicApi
*/
export const platformBrowser: (extraProviders?: StaticProvider[]) => PlatformRef =
createPlatformFactory(platformCore, 'browser', INTERNAL_BROWSER_PLATFORM_PROVIDERS);
/**
* Internal marker to signal whether providers from the `BrowserModule` are already present in DI.
* This is needed to avoid loading `BrowserModule` providers twice. We can't rely on the
* `BrowserModule` presence itself, since the standalone-based bootstrap just imports
* `BrowserModule` providers without referencing the module itself.
*/
const BROWSER_MODULE_PROVIDERS_MARKER = new InjectionToken(
typeof ngDevMode === 'undefined' || ngDevMode ? 'BrowserModule Providers Marker' : '',
);
const TESTABILITY_PROVIDERS = [
{
provide: TESTABILITY_GETTER,
useClass: BrowserGetTestability,
deps: [],
},
{
provide: TESTABILITY,
useClass: Testability,
deps: [NgZone, TestabilityRegistry, TESTABILITY_GETTER],
},
{
provide: Testability, // Also provide as `Testability` for backwards-compatibility.
useClass: Testability,
deps: [NgZone, TestabilityRegistry, TESTABILITY_GETTER],
},
];
const BROWSER_MODULE_PROVIDERS: Provider[] = [
{provide: INJECTOR_SCOPE, useValue: 'root'},
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
{
provide: EVENT_MANAGER_PLUGINS,
useClass: DomEventsPlugin,
multi: true,
deps: [DOCUMENT, NgZone, PLATFORM_ID],
},
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true, deps: [DOCUMENT]},
{
provide: EVENT_MANAGER_PLUGINS,
useClass: EventDelegationPlugin,
multi: true,
},
DomRendererFactory2,
SharedStylesHost,
EventManager,
{provide: RendererFactory2, useExisting: DomRendererFactory2},
{provide: XhrFactory, useClass: BrowserXhr, deps: []},
typeof ngDevMode === 'undefined' || ngDevMode
? {provide: BROWSER_MODULE_PROVIDERS_MARKER, useValue: true}
: [],
];
/**
* Exports required infrastructure for all Angular apps.
* Included by default in all Angular apps created with the CLI
* `new` command.
* Re-exports `CommonModule` and `ApplicationModule`, making their
* exports and providers available to all apps.
*
* @publicApi
*/
@NgModule({
providers: [...BROWSER_MODULE_PROVIDERS, ...TESTABILITY_PROVIDERS],
exports: [CommonModule, ApplicationModule],
})
export class BrowserModule {
constructor(
@Optional()
@SkipSelf()
@Inject(BROWSER_MODULE_PROVIDERS_MARKER)
providersAlreadyPresent: boolean | null,
) {
if ((typeof ngDevMode === 'undefined' || ngDevMode) && providersAlreadyPresent) {
throw new RuntimeError(
RuntimeErrorCode.BROWSER_MODULE_ALREADY_LOADED,
`Providers from the \`BrowserModule\` have already been loaded. If you need access ` +
`to common directives such as NgIf and NgFor, import the \`CommonModule\` instead.`,
);
}
}
/**
* Configures a browser-based app to transition from a server-rendered app, if
* one is present on the page.
*
* @param params An object containing an identifier for the app to transition.
* The ID must match between the client and server versions of the app.
* @returns The reconfigured `BrowserModule` to import into the app's root `AppModule`.
*
* @deprecated Use {@link APP_ID} instead to set the application ID.
*/
static withServerTransition(params: {appId: string}): ModuleWithProviders<BrowserModule> {
return {
ngModule: BrowserModule,
providers: [{provide: APP_ID, useValue: params.appId}],
};
}
}