@angular/upgrade
Version:
Angular - the library for easing update from v1 to v2
794 lines (786 loc) • 38.7 kB
JavaScript
/**
* @license Angular v20.1.6
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
import { module_, UPGRADE_APP_TYPE_KEY, INJECTOR_KEY, LAZY_MODULE_REF, $INJECTOR, $PROVIDE, DOWNGRADED_MODULE_COUNT_KEY, UPGRADE_MODULE_NAME, $SCOPE, $$TESTABILITY, $DELEGATE, $INTERVAL, element, bootstrap } from './constants.mjs';
export { getAngularJSGlobal, getAngularLib, setAngularJSGlobal, setAngularLib, angular1 as ɵangular1, constants as ɵconstants } from './constants.mjs';
import { destroyApp, getDowngradedModuleCount, isNgModuleType, isFunction, UpgradeHelper, controllerKey } from './upgrade_helper.mjs';
export { VERSION, downgradeComponent, downgradeInjectable, upgrade_helper as ɵupgradeHelper, util as ɵutil } from './upgrade_helper.mjs';
import * as i0 from '@angular/core';
import { ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as _NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR, PlatformRef, EventEmitter, Directive, ApplicationRef, ɵNoopNgZone as _NoopNgZone, NgModule, Testability } from '@angular/core';
import { platformBrowser } from '@angular/platform-browser';
// We have to do a little dance to get the ng1 injector into the module injector.
// We store the ng1 injector so that the provider in the module injector can access it
// Then we "get" the ng1 injector from the module injector, which triggers the provider to read
// the stored injector and release the reference to it.
let tempInjectorRef = null;
function setTempInjectorRef(injector) {
tempInjectorRef = injector;
}
function injectorFactory() {
if (!tempInjectorRef) {
throw new Error('Trying to get the AngularJS injector before it being set.');
}
const injector = tempInjectorRef;
tempInjectorRef = null; // clear the value to prevent memory leaks
return injector;
}
function rootScopeFactory(i) {
return i.get('$rootScope');
}
function compileFactory(i) {
return i.get('$compile');
}
function parseFactory(i) {
return i.get('$parse');
}
const angular1Providers = [
// We must use exported named functions for the ng2 factories to keep the compiler happy:
// > Metadata collected contains an error that will be reported at runtime:
// > Function calls are not supported.
// > Consider replacing the function or lambda with a reference to an exported function
{ provide: '$injector', useFactory: injectorFactory, deps: [] },
{ provide: '$rootScope', useFactory: rootScopeFactory, deps: ['$injector'] },
{ provide: '$compile', useFactory: compileFactory, deps: ['$injector'] },
{ provide: '$parse', useFactory: parseFactory, deps: ['$injector'] },
];
class NgAdapterInjector {
modInjector;
constructor(modInjector) {
this.modInjector = modInjector;
}
// When Angular locate a service in the component injector tree, the not found value is set to
// `NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR`. In such a case we should not walk up to the module
// injector.
// AngularJS only supports a single tree and should always check the module injector.
get(token, notFoundValue) {
if (notFoundValue === _NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
return notFoundValue;
}
return this.modInjector.get(token, notFoundValue);
}
}
let moduleUid = 0;
/**
* @description
*
* A helper function for creating an AngularJS module that can bootstrap an Angular module
* "on-demand" (possibly lazily) when a {@link downgradeComponent downgraded component} needs to be
* instantiated.
*
* *Part of the [upgrade/static](api?query=upgrade/static) library for hybrid upgrade apps that
* support AOT compilation.*
*
* It allows loading/bootstrapping the Angular part of a hybrid application lazily and not having to
* pay the cost up-front. For example, you can have an AngularJS application that uses Angular for
* specific routes and only instantiate the Angular modules if/when the user visits one of these
* routes.
*
* The Angular module will be bootstrapped once (when requested for the first time) and the same
* reference will be used from that point onwards.
*
* `downgradeModule()` requires either an `NgModuleFactory`, `NgModule` class or a function:
* - `NgModuleFactory`: If you pass an `NgModuleFactory`, it will be used to instantiate a module
* using `platformBrowser`'s {@link PlatformRef#bootstrapModuleFactory bootstrapModuleFactory()}.
* NOTE: this type of the argument is deprecated. Please either provide an `NgModule` class or a
* bootstrap function instead.
* - `NgModule` class: If you pass an NgModule class, it will be used to instantiate a module
* using `platformBrowser`'s {@link PlatformRef#bootstrapModule bootstrapModule()}.
* - `Function`: If you pass a function, it is expected to return a promise resolving to an
* `NgModuleRef`. The function is called with an array of extra {@link StaticProvider Providers}
* that are expected to be available from the returned `NgModuleRef`'s `Injector`.
*
* `downgradeModule()` returns the name of the created AngularJS wrapper module. You can use it to
* declare a dependency in your main AngularJS module.
*
* {@example upgrade/static/ts/lite/module.ts region="basic-how-to"}
*
* For more details on how to use `downgradeModule()` see
* [Upgrading for Performance](https://angular.io/guide/upgrade).
*
* @usageNotes
*
* Apart from `UpgradeModule`, you can use the rest of the `upgrade/static` helpers as usual to
* build a hybrid application. Note that the Angular pieces (e.g. downgraded services) will not be
* available until the downgraded module has been bootstrapped, i.e. by instantiating a downgraded
* component.
*
* <div class="docs-alert docs-alert-important">
*
* You cannot use `downgradeModule()` and `UpgradeModule` in the same hybrid application.<br />
* Use one or the other.
*
* </div>
*
* ### Differences with `UpgradeModule`
*
* Besides their different API, there are two important internal differences between
* `downgradeModule()` and `UpgradeModule` that affect the behavior of hybrid applications:
*
* 1. Unlike `UpgradeModule`, `downgradeModule()` does not bootstrap the main AngularJS module
* inside the {@link NgZone Angular zone}.
* 2. Unlike `UpgradeModule`, `downgradeModule()` does not automatically run a
* [$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) when changes are
* detected in the Angular part of the application.
*
* What this means is that applications using `UpgradeModule` will run change detection more
* frequently in order to ensure that both frameworks are properly notified about possible changes.
* This will inevitably result in more change detection runs than necessary.
*
* `downgradeModule()`, on the other side, does not try to tie the two change detection systems as
* tightly, restricting the explicit change detection runs only to cases where it knows it is
* necessary (e.g. when the inputs of a downgraded component change). This improves performance,
* especially in change-detection-heavy applications, but leaves it up to the developer to manually
* notify each framework as needed.
*
* For a more detailed discussion of the differences and their implications, see
* [Upgrading for Performance](https://angular.io/guide/upgrade).
*
* <div class="docs-alert docs-alert-helpful">
*
* You can manually trigger a change detection run in AngularJS using
* [scope.$apply(...)](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply) or
* [$rootScope.$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest).
*
* You can manually trigger a change detection run in Angular using {@link NgZone#run
* ngZone.run(...)}.
*
* </div>
*
* ### Downgrading multiple modules
*
* It is possible to downgrade multiple modules and include them in an AngularJS application. In
* that case, each downgraded module will be bootstrapped when an associated downgraded component or
* injectable needs to be instantiated.
*
* Things to keep in mind, when downgrading multiple modules:
*
* - Each downgraded component/injectable needs to be explicitly associated with a downgraded
* module. See `downgradeComponent()` and `downgradeInjectable()` for more details.
*
* - If you want some injectables to be shared among all downgraded modules, you can provide them as
* `StaticProvider`s, when creating the `PlatformRef` (e.g. via `platformBrowser` or
* `platformBrowserDynamic`).
*
* - When using {@link PlatformRef#bootstrapmodule `bootstrapModule()`} or
* {@link PlatformRef#bootstrapmodulefactory `bootstrapModuleFactory()`} to bootstrap the
* downgraded modules, each one is considered a "root" module. As a consequence, a new instance
* will be created for every injectable provided in `"root"` (via
* {@link /api/core/Injectable#providedIn providedIn}
* If this is not your intention, you can have a shared module (that will act as act as the "root"
* module) and create all downgraded modules using that module's injector:
*
* {@example upgrade/static/ts/lite-multi-shared/module.ts region="shared-root-module"}
*
* @publicApi
*/
function downgradeModule(moduleOrBootstrapFn) {
const lazyModuleName = `${UPGRADE_MODULE_NAME}.lazy${++moduleUid}`;
const lazyModuleRefKey = `${LAZY_MODULE_REF}${lazyModuleName}`;
const lazyInjectorKey = `${INJECTOR_KEY}${lazyModuleName}`;
let bootstrapFn;
if (isNgModuleType(moduleOrBootstrapFn)) {
// NgModule class
bootstrapFn = (extraProviders) => platformBrowser(extraProviders).bootstrapModule(moduleOrBootstrapFn);
}
else if (!isFunction(moduleOrBootstrapFn)) {
// NgModule factory
bootstrapFn = (extraProviders) => platformBrowser(extraProviders).bootstrapModuleFactory(moduleOrBootstrapFn);
}
else {
// bootstrap function
bootstrapFn = moduleOrBootstrapFn;
}
let injector;
// Create an ng1 module to bootstrap.
module_(lazyModuleName, [])
.constant(UPGRADE_APP_TYPE_KEY, 3 /* ɵutil.UpgradeAppType.Lite */)
.factory(INJECTOR_KEY, [lazyInjectorKey, identity])
.factory(lazyInjectorKey, () => {
if (!injector) {
throw new Error('Trying to get the Angular injector before bootstrapping the corresponding ' +
'Angular module.');
}
return injector;
})
.factory(LAZY_MODULE_REF, [lazyModuleRefKey, identity])
.factory(lazyModuleRefKey, [
$INJECTOR,
($injector) => {
setTempInjectorRef($injector);
const result = {
promise: bootstrapFn(angular1Providers).then((ref) => {
injector = result.injector = new NgAdapterInjector(ref.injector);
injector.get($INJECTOR);
// Destroy the AngularJS app once the Angular `PlatformRef` is destroyed.
// This does not happen in a typical SPA scenario, but it might be useful for
// other use-cases where disposing of an Angular/AngularJS app is necessary
// (such as Hot Module Replacement (HMR)).
// See https://github.com/angular/angular/issues/39935.
injector.get(PlatformRef).onDestroy(() => destroyApp($injector));
return injector;
}),
};
return result;
},
])
.config([
$INJECTOR,
$PROVIDE,
($injector, $provide) => {
$provide.constant(DOWNGRADED_MODULE_COUNT_KEY, getDowngradedModuleCount($injector) + 1);
},
]);
return lazyModuleName;
}
function identity(x) {
return x;
}
const NOT_SUPPORTED = 'NOT_SUPPORTED';
const INITIAL_VALUE = {
__UNINITIALIZED__: true,
};
class Bindings {
twoWayBoundProperties = [];
twoWayBoundLastValues = [];
expressionBoundProperties = [];
propertyToOutputMap = {};
}
/**
* @description
*
* A helper class that allows an AngularJS component to be used from Angular.
*
* *Part of the [upgrade/static](api?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AOT compilation.*
*
* This helper class should be used as a base class for creating Angular directives
* that wrap AngularJS components that need to be "upgraded".
*
* @usageNotes
* ### Examples
*
* Let's assume that you have an AngularJS component called `ng1Hero` that needs
* to be made available in Angular templates.
*
* {@example upgrade/static/ts/full/module.ts region="ng1-hero"}
*
* We must create a `Directive` that will make this AngularJS component
* available inside Angular templates.
*
* {@example upgrade/static/ts/full/module.ts region="ng1-hero-wrapper"}
*
* In this example you can see that we must derive from the `UpgradeComponent`
* base class but also provide an {@link Directive `@Directive`} decorator. This is
* because the AOT compiler requires that this information is statically available at
* compile time.
*
* Note that we must do the following:
* * specify the directive's selector (`ng1-hero`)
* * specify all inputs and outputs that the AngularJS component expects
* * derive from `UpgradeComponent`
* * call the base class from the constructor, passing
* * the AngularJS name of the component (`ng1Hero`)
* * the `ElementRef` and `Injector` for the component wrapper
*
* @publicApi
* @extensible
*/
class UpgradeComponent {
helper;
$element;
$componentScope;
directive;
bindings;
controllerInstance;
bindingDestination;
// We will be instantiating the controller in the `ngOnInit` hook, when the
// first `ngOnChanges` will have been already triggered. We store the
// `SimpleChanges` and "play them back" later.
pendingChanges = null;
unregisterDoCheckWatcher;
/**
* Create a new `UpgradeComponent` instance. You should not normally need to do this.
* Instead you should derive a new class from this one and call the super constructor
* from the base class.
*
* {@example upgrade/static/ts/full/module.ts region="ng1-hero-wrapper" }
*
* * The `name` parameter should be the name of the AngularJS directive.
* * The `elementRef` and `injector` parameters should be acquired from Angular by dependency
* injection into the base class constructor.
*/
constructor(name, elementRef, injector) {
this.helper = new UpgradeHelper(injector, name, elementRef);
this.$element = this.helper.$element;
this.directive = this.helper.directive;
this.bindings = this.initializeBindings(this.directive, name);
// We ask for the AngularJS scope from the Angular injector, since
// we will put the new component scope onto the new injector for each component
const $parentScope = injector.get($SCOPE);
// QUESTION 1: Should we create an isolated scope if the scope is only true?
// QUESTION 2: Should we make the scope accessible through `$element.scope()/isolateScope()`?
this.$componentScope = $parentScope.$new(!!this.directive.scope);
this.initializeOutputs();
}
/** @docs-private */
ngOnInit() {
// Collect contents, insert and compile template
const attachChildNodes = this.helper.prepareTransclusion();
const linkFn = this.helper.compileTemplate();
// Instantiate controller
const controllerType = this.directive.controller;
const bindToController = this.directive.bindToController;
let controllerInstance = controllerType
? this.helper.buildController(controllerType, this.$componentScope)
: undefined;
let bindingDestination;
if (!bindToController) {
bindingDestination = this.$componentScope;
}
else if (controllerType && controllerInstance) {
bindingDestination = controllerInstance;
}
else {
throw new Error(`Upgraded directive '${this.directive.name}' specifies 'bindToController' but no controller.`);
}
this.controllerInstance = controllerInstance;
this.bindingDestination = bindingDestination;
// Set up outputs
this.bindOutputs(bindingDestination);
// Require other controllers
const requiredControllers = this.helper.resolveAndBindRequiredControllers(controllerInstance);
// Hook: $onChanges
if (this.pendingChanges) {
this.forwardChanges(this.pendingChanges, bindingDestination);
this.pendingChanges = null;
}
// Hook: $onInit
if (this.controllerInstance && isFunction(this.controllerInstance.$onInit)) {
this.controllerInstance.$onInit();
}
// Hook: $doCheck
if (controllerInstance && isFunction(controllerInstance.$doCheck)) {
const callDoCheck = () => controllerInstance?.$doCheck?.();
this.unregisterDoCheckWatcher = this.$componentScope.$parent.$watch(callDoCheck);
callDoCheck();
}
// Linking
const link = this.directive.link;
const preLink = typeof link == 'object' && link.pre;
const postLink = typeof link == 'object' ? link.post : link;
const attrs = NOT_SUPPORTED;
const transcludeFn = NOT_SUPPORTED;
if (preLink) {
preLink(this.$componentScope, this.$element, attrs, requiredControllers, transcludeFn);
}
linkFn(this.$componentScope, null, { parentBoundTranscludeFn: attachChildNodes });
if (postLink) {
postLink(this.$componentScope, this.$element, attrs, requiredControllers, transcludeFn);
}
// Hook: $postLink
if (this.controllerInstance && isFunction(this.controllerInstance.$postLink)) {
this.controllerInstance.$postLink();
}
}
/** @docs-private */
ngOnChanges(changes) {
if (!this.bindingDestination) {
this.pendingChanges = changes;
}
else {
this.forwardChanges(changes, this.bindingDestination);
}
}
/** @docs-private */
ngDoCheck() {
const twoWayBoundProperties = this.bindings.twoWayBoundProperties;
const twoWayBoundLastValues = this.bindings.twoWayBoundLastValues;
const propertyToOutputMap = this.bindings.propertyToOutputMap;
twoWayBoundProperties.forEach((propName, idx) => {
const newValue = this.bindingDestination?.[propName];
const oldValue = twoWayBoundLastValues[idx];
if (!Object.is(newValue, oldValue)) {
const outputName = propertyToOutputMap[propName];
const eventEmitter = this[outputName];
eventEmitter.emit(newValue);
twoWayBoundLastValues[idx] = newValue;
}
});
}
/** @docs-private */
ngOnDestroy() {
if (isFunction(this.unregisterDoCheckWatcher)) {
this.unregisterDoCheckWatcher();
}
this.helper.onDestroy(this.$componentScope, this.controllerInstance);
}
initializeBindings(directive, name) {
const btcIsObject = typeof directive.bindToController === 'object';
if (btcIsObject && Object.keys(directive.scope).length) {
throw new Error(`Binding definitions on scope and controller at the same time is not supported.`);
}
const context = btcIsObject ? directive.bindToController : directive.scope;
const bindings = new Bindings();
if (typeof context == 'object') {
Object.keys(context).forEach((propName) => {
const definition = context[propName];
const bindingType = definition.charAt(0);
// QUESTION: What about `=*`? Ignore? Throw? Support?
switch (bindingType) {
case '@':
case '<':
// We don't need to do anything special. They will be defined as inputs on the
// upgraded component facade and the change propagation will be handled by
// `ngOnChanges()`.
break;
case '=':
bindings.twoWayBoundProperties.push(propName);
bindings.twoWayBoundLastValues.push(INITIAL_VALUE);
bindings.propertyToOutputMap[propName] = propName + 'Change';
break;
case '&':
bindings.expressionBoundProperties.push(propName);
bindings.propertyToOutputMap[propName] = propName;
break;
default:
let json = JSON.stringify(context);
throw new Error(`Unexpected mapping '${bindingType}' in '${json}' in '${name}' directive.`);
}
});
}
return bindings;
}
initializeOutputs() {
// Initialize the outputs for `=` and `&` bindings
this.bindings.twoWayBoundProperties
.concat(this.bindings.expressionBoundProperties)
.forEach((propName) => {
const outputName = this.bindings.propertyToOutputMap[propName];
this[outputName] = new EventEmitter();
});
}
bindOutputs(bindingDestination) {
// Bind `&` bindings to the corresponding outputs
this.bindings.expressionBoundProperties.forEach((propName) => {
const outputName = this.bindings.propertyToOutputMap[propName];
const emitter = this[outputName];
bindingDestination[propName] = (value) => emitter.emit(value);
});
}
forwardChanges(changes, bindingDestination) {
// Forward input changes to `bindingDestination`
Object.keys(changes).forEach((propName) => (bindingDestination[propName] = changes[propName].currentValue));
if (isFunction(bindingDestination.$onChanges)) {
bindingDestination.$onChanges(changes);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: UpgradeComponent, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.6", type: UpgradeComponent, isStandalone: true, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: UpgradeComponent, decorators: [{
type: Directive
}], ctorParameters: () => [{ type: undefined }, { type: i0.ElementRef }, { type: i0.Injector }] });
/**
* @description
*
* An `NgModule`, which you import to provide AngularJS core services,
* and has an instance method used to bootstrap the hybrid upgrade application.
*
* *Part of the [upgrade/static](api?query=upgrade/static)
* library for hybrid upgrade apps that support AOT compilation*
*
* The `upgrade/static` package contains helpers that allow AngularJS and Angular components
* to be used together inside a hybrid upgrade application, which supports AOT compilation.
*
* Specifically, the classes and functions in the `upgrade/static` module allow the following:
*
* 1. Creation of an Angular directive that wraps and exposes an AngularJS component so
* that it can be used in an Angular template. See `UpgradeComponent`.
* 2. Creation of an AngularJS directive that wraps and exposes an Angular component so
* that it can be used in an AngularJS template. See `downgradeComponent`.
* 3. Creation of an Angular root injector provider that wraps and exposes an AngularJS
* service so that it can be injected into an Angular context. See
* {@link UpgradeModule#upgrading-an-angular-1-service Upgrading an AngularJS service} below.
* 4. Creation of an AngularJS service that wraps and exposes an Angular injectable
* so that it can be injected into an AngularJS context. See `downgradeInjectable`.
* 5. Bootstrapping of a hybrid Angular application which contains both of the frameworks
* coexisting in a single application.
*
* @usageNotes
*
* ```ts
* import {UpgradeModule} from '@angular/upgrade/static';
* ```
*
* See also the {@link UpgradeModule#examples examples} below.
*
* ### Mental Model
*
* When reasoning about how a hybrid application works it is useful to have a mental model which
* describes what is happening and explains what is happening at the lowest level.
*
* 1. There are two independent frameworks running in a single application, each framework treats
* the other as a black box.
* 2. Each DOM element on the page is owned exactly by one framework. Whichever framework
* instantiated the element is the owner. Each framework only updates/interacts with its own
* DOM elements and ignores others.
* 3. AngularJS directives always execute inside the AngularJS framework codebase regardless of
* where they are instantiated.
* 4. Angular components always execute inside the Angular framework codebase regardless of
* where they are instantiated.
* 5. An AngularJS component can be "upgraded"" to an Angular component. This is achieved by
* defining an Angular directive, which bootstraps the AngularJS component at its location
* in the DOM. See `UpgradeComponent`.
* 6. An Angular component can be "downgraded" to an AngularJS component. This is achieved by
* defining an AngularJS directive, which bootstraps the Angular component at its location
* in the DOM. See `downgradeComponent`.
* 7. Whenever an "upgraded"/"downgraded" component is instantiated the host element is owned by
* the framework doing the instantiation. The other framework then instantiates and owns the
* view for that component.
* 1. This implies that the component bindings will always follow the semantics of the
* instantiation framework.
* 2. The DOM attributes are parsed by the framework that owns the current template. So
* attributes in AngularJS templates must use kebab-case, while AngularJS templates must use
* camelCase.
* 3. However the template binding syntax will always use the Angular style, e.g. square
* brackets (`[...]`) for property binding.
* 8. Angular is bootstrapped first; AngularJS is bootstrapped second. AngularJS always owns the
* root component of the application.
* 9. The new application is running in an Angular zone, and therefore it no longer needs calls to
* `$apply()`.
*
* ### The `UpgradeModule` class
*
* This class is an `NgModule`, which you import to provide AngularJS core services,
* and has an instance method used to bootstrap the hybrid upgrade application.
*
* * Core AngularJS services<br />
* Importing this `NgModule` will add providers for the core
* [AngularJS services](https://docs.angularjs.org/api/ng/service) to the root injector.
*
* * Bootstrap<br />
* The runtime instance of this class contains a {@link UpgradeModule#bootstrap `bootstrap()`}
* method, which you use to bootstrap the top level AngularJS module onto an element in the
* DOM for the hybrid upgrade app.
*
* It also contains properties to access the {@link UpgradeModule#injector root injector}, the
* bootstrap `NgZone` and the
* [AngularJS $injector](https://docs.angularjs.org/api/auto/service/$injector).
*
* ### Examples
*
* Import the `UpgradeModule` into your top level Angular {@link NgModule NgModule}.
*
* {@example upgrade/static/ts/full/module.ts region='ng2-module'}
*
* Then inject `UpgradeModule` into your Angular `NgModule` and use it to bootstrap the top level
* [AngularJS module](https://docs.angularjs.org/api/ng/type/angular.Module) in the
* `ngDoBootstrap()` method.
*
* {@example upgrade/static/ts/full/module.ts region='bootstrap-ng1'}
*
* Finally, kick off the whole process, by bootstrapping your top level Angular `NgModule`.
*
* {@example upgrade/static/ts/full/module.ts region='bootstrap-ng2'}
*
* ### Upgrading an AngularJS service
*
* There is no specific API for upgrading an AngularJS service. Instead you should just follow the
* following recipe:
*
* Let's say you have an AngularJS service:
*
* {@example upgrade/static/ts/full/module.ts region="ng1-text-formatter-service"}
*
* Then you should define an Angular provider to be included in your `NgModule` `providers`
* property.
*
* {@example upgrade/static/ts/full/module.ts region="upgrade-ng1-service"}
*
* Then you can use the "upgraded" AngularJS service by injecting it into an Angular component
* or service.
*
* {@example upgrade/static/ts/full/module.ts region="use-ng1-upgraded-service"}
*
* @publicApi
*/
class UpgradeModule {
ngZone;
platformRef;
/**
* The AngularJS `$injector` for the upgrade application.
*/
$injector;
/** The Angular Injector **/
injector;
applicationRef;
constructor(
/** The root `Injector` for the upgrade application. */
injector,
/** The bootstrap zone for the upgrade application */
ngZone,
/**
* The owning `NgModuleRef`s `PlatformRef` instance.
* This is used to tie the lifecycle of the bootstrapped AngularJS apps to that of the Angular
* `PlatformRef`.
*/
platformRef) {
this.ngZone = ngZone;
this.platformRef = platformRef;
this.injector = new NgAdapterInjector(injector);
this.applicationRef = this.injector.get(ApplicationRef);
}
/**
* Bootstrap an AngularJS application from this NgModule
* @param element the element on which to bootstrap the AngularJS application
* @param [modules] the AngularJS modules to bootstrap for this application
* @param [config] optional extra AngularJS bootstrap configuration
* @return The value returned by
* [angular.bootstrap()](https://docs.angularjs.org/api/ng/function/angular.bootstrap).
*/
bootstrap(element$1, modules = [], config /*angular.IAngularBootstrapConfig*/) {
const INIT_MODULE_NAME = UPGRADE_MODULE_NAME + '.init';
// Create an ng1 module to bootstrap
module_(INIT_MODULE_NAME, [])
.constant(UPGRADE_APP_TYPE_KEY, 2 /* ɵutil.UpgradeAppType.Static */)
.value(INJECTOR_KEY, this.injector)
.factory(LAZY_MODULE_REF, [
INJECTOR_KEY,
(injector) => ({ injector }),
])
.config([
$PROVIDE,
$INJECTOR,
($provide, $injector) => {
if ($injector.has($$TESTABILITY)) {
$provide.decorator($$TESTABILITY, [
$DELEGATE,
(testabilityDelegate) => {
const originalWhenStable = testabilityDelegate.whenStable;
const injector = this.injector;
// Cannot use arrow function below because we need the context
const newWhenStable = function (callback) {
originalWhenStable.call(testabilityDelegate, function () {
const ng2Testability = injector.get(Testability);
if (ng2Testability.isStable()) {
callback();
}
else {
ng2Testability.whenStable(newWhenStable.bind(testabilityDelegate, callback));
}
});
};
testabilityDelegate.whenStable = newWhenStable;
return testabilityDelegate;
},
]);
}
if ($injector.has($INTERVAL)) {
$provide.decorator($INTERVAL, [
$DELEGATE,
(intervalDelegate) => {
// Wrap the $interval service so that setInterval is called outside NgZone,
// but the callback is still invoked within it. This is so that $interval
// won't block stability, which preserves the behavior from AngularJS.
let wrappedInterval = (fn, delay, count, invokeApply, ...pass) => {
return this.ngZone.runOutsideAngular(() => {
return intervalDelegate((...args) => {
// Run callback in the next VM turn - $interval calls
// $rootScope.$apply, and running the callback in NgZone will
// cause a '$digest already in progress' error if it's in the
// same vm turn.
setTimeout(() => {
this.ngZone.run(() => fn(...args));
});
}, delay, count, invokeApply, ...pass);
});
};
Object.keys(intervalDelegate).forEach((prop) => (wrappedInterval[prop] = intervalDelegate[prop]));
// the `flush` method will be present when ngMocks is used
if (intervalDelegate.hasOwnProperty('flush')) {
wrappedInterval['flush'] = () => {
intervalDelegate['flush']();
return wrappedInterval;
};
}
return wrappedInterval;
},
]);
}
},
])
.run([
$INJECTOR,
($injector) => {
this.$injector = $injector;
const $rootScope = $injector.get('$rootScope');
// Initialize the ng1 $injector provider
setTempInjectorRef($injector);
this.injector.get($INJECTOR);
// Put the injector on the DOM, so that it can be "required"
element(element$1).data(controllerKey(INJECTOR_KEY), this.injector);
// Destroy the AngularJS app once the Angular `PlatformRef` is destroyed.
// This does not happen in a typical SPA scenario, but it might be useful for
// other use-cases where disposing of an Angular/AngularJS app is necessary
// (such as Hot Module Replacement (HMR)).
// See https://github.com/angular/angular/issues/39935.
this.platformRef.onDestroy(() => destroyApp($injector));
// Wire up the ng1 rootScope to run a digest cycle whenever the zone settles
// We need to do this in the next tick so that we don't prevent the bootup stabilizing
setTimeout(() => {
const synchronize = () => {
this.ngZone.run(() => {
if ($rootScope.$$phase) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
console.warn('A digest was triggered while one was already in progress. This may mean that something is triggering digests outside the Angular zone.');
}
$rootScope.$evalAsync();
}
else {
$rootScope.$digest();
}
});
};
const subscription =
// We _DO NOT_ usually want to have any code that does one thing for zoneless and another for ZoneJS.
// This is only here because there is not enough coverage for hybrid apps anymore so we cannot
// be confident that making UpgradeModule work with zoneless is a non-breaking change.
this.ngZone instanceof _NoopNgZone
? this.applicationRef.afterTick.subscribe(() => synchronize())
: this.ngZone.onMicrotaskEmpty.subscribe(() => synchronize());
$rootScope.$on('$destroy', () => {
subscription.unsubscribe();
});
}, 0);
},
]);
const upgradeModule = module_(UPGRADE_MODULE_NAME, [INIT_MODULE_NAME].concat(modules));
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
const windowAngular = window['angular'];
windowAngular.resumeBootstrap = undefined;
// Bootstrap the AngularJS application inside our zone
const returnValue = this.ngZone.run(() => bootstrap(element$1, [upgradeModule.name], config));
// Patch resumeBootstrap() to run inside the ngZone
if (windowAngular.resumeBootstrap) {
const originalResumeBootstrap = windowAngular.resumeBootstrap;
const ngZone = this.ngZone;
windowAngular.resumeBootstrap = function () {
let args = arguments;
windowAngular.resumeBootstrap = originalResumeBootstrap;
return ngZone.run(() => windowAngular.resumeBootstrap.apply(this, args));
};
}
return returnValue;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: UpgradeModule, deps: [{ token: i0.Injector }, { token: i0.NgZone }, { token: i0.PlatformRef }], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.6", ngImport: i0, type: UpgradeModule });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: UpgradeModule, providers: [angular1Providers] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: UpgradeModule, decorators: [{
type: NgModule,
args: [{ providers: [angular1Providers] }]
}], ctorParameters: () => [{ type: i0.Injector }, { type: i0.NgZone }, { type: i0.PlatformRef }] });
export { UpgradeComponent, UpgradeModule, downgradeModule };
//# sourceMappingURL=static.mjs.map