@angular/upgrade
Version:
Angular - the library for easing update from v1 to v2
887 lines (882 loc) • 36.8 kB
JavaScript
/**
* @license Angular v20.1.6
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
import { UpgradeHelper, trustedHTMLFromLegacyTemplate, isFunction, strictEquals, downgradeComponent, onError, controllerKey, downgradeInjectable, Deferred, destroyApp } from './upgrade_helper.mjs';
export { VERSION } from './upgrade_helper.mjs';
import { __decorate, __metadata } from 'tslib';
import * as i0 from '@angular/core';
import { Inject, Injector, ElementRef, Directive, EventEmitter, NgZone, Compiler, NgModule, resolveForwardRef, Testability } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { $SCOPE, bootstrap, element, INJECTOR_KEY, $INJECTOR, module_, UPGRADE_APP_TYPE_KEY, LAZY_MODULE_REF, NG_ZONE_KEY, COMPILER_KEY, $ROOT_SCOPE, $$TESTABILITY, $COMPILE } from './constants.mjs';
const CAMEL_CASE = /([A-Z])/g;
const INITIAL_VALUE = {
__UNINITIALIZED__: true,
};
const NOT_SUPPORTED = 'NOT_SUPPORTED';
function getInputPropertyMapName(name) {
return `input_${name}`;
}
function getOutputPropertyMapName(name) {
return `output_${name}`;
}
class UpgradeNg1ComponentAdapterBuilder {
name;
type;
inputs = [];
inputsRename = [];
outputs = [];
outputsRename = [];
propertyOutputs = [];
checkProperties = [];
propertyMap = {};
directive = null;
template;
constructor(name) {
this.name = name;
const selector = name.replace(CAMEL_CASE, (all, next) => '-' + next.toLowerCase());
const self = this;
let MyClass = class MyClass extends UpgradeNg1ComponentAdapter {
constructor(scope, injector, elementRef) {
super(new UpgradeHelper(injector, name, elementRef, self.directive || undefined), scope, self.template, self.inputs, self.outputs, self.propertyOutputs, self.checkProperties, self.propertyMap);
}
static ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [$SCOPE,] }] },
{ type: Injector },
{ type: ElementRef }
];
};
MyClass = __decorate([
Directive({
jit: true,
selector: selector,
inputs: this.inputsRename,
outputs: this.outputsRename,
standalone: false,
}),
__metadata("design:paramtypes", [Object, Injector, ElementRef])
], MyClass);
this.type = MyClass;
}
extractBindings() {
const btcIsObject = typeof this.directive.bindToController === 'object';
if (btcIsObject && Object.keys(this.directive.scope).length) {
throw new Error(`Binding definitions on scope and controller at the same time are not supported.`);
}
const context = btcIsObject ? this.directive.bindToController : this.directive.scope;
if (typeof context == 'object') {
Object.keys(context).forEach((propName) => {
const definition = context[propName];
const bindingType = definition.charAt(0);
const bindingOptions = definition.charAt(1);
const attrName = definition.substring(bindingOptions === '?' ? 2 : 1) || propName;
// QUESTION: What about `=*`? Ignore? Throw? Support?
const inputName = getInputPropertyMapName(attrName);
const inputNameRename = `${inputName}: ${attrName}`;
const outputName = getOutputPropertyMapName(attrName);
const outputNameRename = `${outputName}: ${attrName}`;
const outputNameRenameChange = `${outputNameRename}Change`;
switch (bindingType) {
case '@':
case '<':
this.inputs.push(inputName);
this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = propName;
break;
case '=':
this.inputs.push(inputName);
this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = propName;
this.outputs.push(outputName);
this.outputsRename.push(outputNameRenameChange);
this.propertyMap[outputName] = propName;
this.checkProperties.push(propName);
this.propertyOutputs.push(outputName);
break;
case '&':
this.outputs.push(outputName);
this.outputsRename.push(outputNameRename);
this.propertyMap[outputName] = propName;
break;
default:
let json = JSON.stringify(context);
throw new Error(`Unexpected mapping '${bindingType}' in '${json}' in '${this.name}' directive.`);
}
});
}
}
/**
* Upgrade ng1 components into Angular.
*/
static resolve(exportedComponents, $injector) {
const promises = Object.entries(exportedComponents).map(([name, exportedComponent]) => {
exportedComponent.directive = UpgradeHelper.getDirective($injector, name);
exportedComponent.extractBindings();
return Promise.resolve(UpgradeHelper.getTemplate($injector, exportedComponent.directive, true)).then((template) => (exportedComponent.template = template));
});
return Promise.all(promises);
}
}
class UpgradeNg1ComponentAdapter {
helper;
template;
inputs;
outputs;
propOuts;
checkProperties;
propertyMap;
controllerInstance = null;
destinationObj = null;
checkLastValues = [];
directive;
element;
$element = null;
componentScope;
constructor(helper, scope, template, inputs, outputs, propOuts, checkProperties, propertyMap) {
this.helper = helper;
this.template = template;
this.inputs = inputs;
this.outputs = outputs;
this.propOuts = propOuts;
this.checkProperties = checkProperties;
this.propertyMap = propertyMap;
this.directive = helper.directive;
this.element = helper.element;
this.$element = helper.$element;
this.componentScope = scope.$new(!!this.directive.scope);
const controllerType = this.directive.controller;
if (this.directive.bindToController && controllerType) {
this.controllerInstance = this.helper.buildController(controllerType, this.componentScope);
this.destinationObj = this.controllerInstance;
}
else {
this.destinationObj = this.componentScope;
}
for (const input of this.inputs) {
this[input] = null;
}
for (const output of this.outputs) {
const emitter = (this[output] = new EventEmitter());
if (this.propOuts.indexOf(output) === -1) {
this.setComponentProperty(output, ((emitter) => (value) => emitter.emit(value))(emitter));
}
}
this.checkLastValues.push(...Array(propOuts.length).fill(INITIAL_VALUE));
}
ngOnInit() {
// Collect contents, insert and compile template
const attachChildNodes = this.helper.prepareTransclusion();
const linkFn = this.helper.compileTemplate(trustedHTMLFromLegacyTemplate(this.template));
// Instantiate controller (if not already done so)
const controllerType = this.directive.controller;
const bindToController = this.directive.bindToController;
if (controllerType && !bindToController) {
this.controllerInstance = this.helper.buildController(controllerType, this.componentScope);
}
// Require other controllers
const requiredControllers = this.helper.resolveAndBindRequiredControllers(this.controllerInstance);
// Hook: $onInit
if (this.controllerInstance && isFunction(this.controllerInstance.$onInit)) {
this.controllerInstance.$onInit();
}
// 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();
}
}
ngOnChanges(changes) {
const ng1Changes = {};
Object.keys(changes).forEach((propertyMapName) => {
const change = changes[propertyMapName];
this.setComponentProperty(propertyMapName, change.currentValue);
ng1Changes[this.propertyMap[propertyMapName]] = change;
});
if (isFunction(this.destinationObj.$onChanges)) {
this.destinationObj.$onChanges(ng1Changes);
}
}
ngDoCheck() {
const destinationObj = this.destinationObj;
const lastValues = this.checkLastValues;
const checkProperties = this.checkProperties;
const propOuts = this.propOuts;
checkProperties.forEach((propName, i) => {
const value = destinationObj[propName];
const last = lastValues[i];
if (!strictEquals(last, value)) {
const eventEmitter = this[propOuts[i]];
eventEmitter.emit((lastValues[i] = value));
}
});
if (this.controllerInstance && isFunction(this.controllerInstance.$doCheck)) {
this.controllerInstance.$doCheck();
}
}
ngOnDestroy() {
this.helper.onDestroy(this.componentScope, this.controllerInstance);
}
setComponentProperty(name, value) {
this.destinationObj[this.propertyMap[name]] = value;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: UpgradeNg1ComponentAdapter, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.6", type: UpgradeNg1ComponentAdapter, isStandalone: true, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: UpgradeNg1ComponentAdapter, decorators: [{
type: Directive
}], ctorParameters: () => [{ type: UpgradeHelper }, { type: undefined }, { type: undefined }, { type: undefined }, { type: undefined }, { type: undefined }, { type: undefined }, { type: undefined }] });
let upgradeCount = 0;
/**
* Use `UpgradeAdapter` to allow AngularJS and Angular to coexist in a single application.
*
* The `UpgradeAdapter` allows:
* 1. creation of Angular component from AngularJS component directive
* (See {@link UpgradeAdapter#upgradeNg1Component})
* 2. creation of AngularJS directive from Angular component.
* (See {@link UpgradeAdapter#downgradeNg2Component})
* 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks
* coexisting in a single application.
*
* @usageNotes
* ### 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 AngularJS framework codebase regardless of
* where they are instantiated.
* 4. Angular components always execute inside Angular framework codebase regardless of
* where they are instantiated.
* 5. An AngularJS component can be upgraded to an Angular component. This creates an
* Angular directive, which bootstraps the AngularJS component directive in that location.
* 6. An Angular component can be downgraded to an AngularJS component directive. This creates
* an AngularJS directive, which bootstraps the Angular component in that location.
* 7. Whenever an adapter 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. This implies that component bindings will always follow the semantics of the
* instantiation framework. The syntax is always that of Angular syntax.
* 8. AngularJS is always bootstrapped first and owns the bottom most view.
* 9. The new application is running in Angular zone, and therefore it no longer needs calls to
* `$apply()`.
*
* ### Example
*
* ```ts
* const adapter = new UpgradeAdapter(forwardRef(() => MyNg2Module), myCompilerOptions);
* const module = angular.module('myExample', []);
* module.directive('ng2Comp', adapter.downgradeNg2Component(Ng2Component));
*
* module.directive('ng1Hello', function() {
* return {
* scope: { title: '=' },
* template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
* };
* });
*
*
* @Component({
* selector: 'ng2-comp',
* inputs: ['name'],
* template: 'ng2[<ng1-hello [title]="name">transclude</ng1-hello>](<ng-content></ng-content>)',
* directives:
* })
* class Ng2Component {
* }
*
* @NgModule({
* declarations: [Ng2Component, adapter.upgradeNg1Component('ng1Hello')],
* imports: [BrowserModule]
* })
* class MyNg2Module {}
*
*
* document.body.innerHTML = '<ng2-comp name="World">project</ng2-comp>';
*
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
* expect(document.body.textContent).toEqual(
* "ng2[ng1[Hello World!](transclude)](project)");
* });
*
* ```
*
* @deprecated Deprecated since v5. Use `upgrade/static` instead, which also supports
* [Ahead-of-Time compilation](tools/cli/aot-compiler).
* @publicApi
*/
class UpgradeAdapter {
ng2AppModule;
compilerOptions;
idPrefix = `NG2_UPGRADE_${upgradeCount++}_`;
downgradedComponents = [];
/**
* An internal map of ng1 components which need to up upgraded to ng2.
*
* We can't upgrade until injector is instantiated and we can retrieve the component metadata.
* For this reason we keep a list of components to upgrade until ng1 injector is bootstrapped.
*
* @internal
*/
ng1ComponentsToBeUpgraded = {};
upgradedProviders = [];
moduleRef = null;
constructor(ng2AppModule, compilerOptions) {
this.ng2AppModule = ng2AppModule;
this.compilerOptions = compilerOptions;
if (!ng2AppModule) {
throw new Error('UpgradeAdapter cannot be instantiated without an NgModule of the Angular app.');
}
}
/**
* Allows Angular Component to be used from AngularJS.
*
* Use `downgradeNg2Component` to create an AngularJS Directive Definition Factory from
* Angular Component. The adapter will bootstrap Angular component from within the
* AngularJS template.
*
* @usageNotes
* ### Mental Model
*
* 1. The component is instantiated by being listed in AngularJS template. This means that the
* host element is controlled by AngularJS, but the component's view will be controlled by
* Angular.
* 2. Even thought the component is instantiated in AngularJS, it will be using Angular
* syntax. This has to be done, this way because we must follow Angular components do not
* declare how the attributes should be interpreted.
* 3. `ng-model` is controlled by AngularJS and communicates with the downgraded Angular component
* by way of the `ControlValueAccessor` interface from @angular/forms. Only components that
* implement this interface are eligible.
*
* ### Supported Features
*
* - Bindings:
* - Attribute: `<comp name="World">`
* - Interpolation: `<comp greeting="Hello {{name}}!">`
* - Expression: `<comp [name]="username">`
* - Event: `<comp (close)="doSomething()">`
* - ng-model: `<comp ng-model="name">`
* - Content projection: yes
*
* ### Example
*
* ```angular-ts
* const adapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
* const module = angular.module('myExample', []);
* module.directive('greet', adapter.downgradeNg2Component(Greeter));
*
* @Component({
* selector: 'greet',
* template: '{{salutation()}} {{name()}}! - <ng-content></ng-content>'
* })
* class Greeter {
* salutation = input.required<string>();
* name: input.required<string>();
* }
*
* @NgModule({
* declarations: [Greeter],
* imports: [BrowserModule]
* })
* class MyNg2Module {}
*
* document.body.innerHTML =
* 'ng1 template: <greet salutation="Hello" [name]="world">text</greet>';
*
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
* expect(document.body.textContent).toEqual("ng1 template: Hello world! - text");
* });
* ```
*/
downgradeNg2Component(component) {
this.downgradedComponents.push(component);
return downgradeComponent({ component });
}
/**
* Allows AngularJS Component to be used from Angular.
*
* Use `upgradeNg1Component` to create an Angular component from AngularJS Component
* directive. The adapter will bootstrap AngularJS component from within the Angular
* template.
*
* @usageNotes
* ### Mental Model
*
* 1. The component is instantiated by being listed in Angular template. This means that the
* host element is controlled by Angular, but the component's view will be controlled by
* AngularJS.
*
* ### Supported Features
*
* - Bindings:
* - Attribute: `<comp name="World">`
* - Interpolation: `<comp greeting="Hello {{name}}!">`
* - Expression: `<comp [name]="username">`
* - Event: `<comp (close)="doSomething()">`
* - Transclusion: yes
* - Only some of the features of
* [Directive Definition Object](https://docs.angularjs.org/api/ng/service/$compile) are
* supported:
* - `compile`: not supported because the host element is owned by Angular, which does
* not allow modifying DOM structure during compilation.
* - `controller`: supported. (NOTE: injection of `$attrs` and `$transclude` is not supported.)
* - `controllerAs`: supported.
* - `bindToController`: supported.
* - `link`: supported. (NOTE: only pre-link function is supported.)
* - `name`: supported.
* - `priority`: ignored.
* - `replace`: not supported.
* - `require`: supported.
* - `restrict`: must be set to 'E'.
* - `scope`: supported.
* - `template`: supported.
* - `templateUrl`: supported.
* - `terminal`: ignored.
* - `transclude`: supported.
*
*
* ### Example
*
* ```angular-ts
* const adapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
* const module = angular.module('myExample', []);
*
* module.directive('greet', function() {
* return {
* scope: {salutation: '=', name: '=' },
* template: '{{salutation}} {{name}}! - <span ng-transclude></span>'
* };
* });
*
* module.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
*
* @Component({
* selector: 'ng2',
* template: 'ng2 template: <greet salutation="Hello" [name]="world">text</greet>'
* })
* class Ng2Component {
* }
*
* @NgModule({
* declarations: [Ng2Component, adapter.upgradeNg1Component('greet')],
* imports: [BrowserModule]
* })
* class MyNg2Module {}
*
* document.body.innerHTML = '<ng2></ng2>';
*
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
* expect(document.body.textContent).toEqual("ng2 template: Hello world! - text");
* });
* ```
*/
upgradeNg1Component(name) {
if (this.ng1ComponentsToBeUpgraded.hasOwnProperty(name)) {
return this.ng1ComponentsToBeUpgraded[name].type;
}
else {
return (this.ng1ComponentsToBeUpgraded[name] = new UpgradeNg1ComponentAdapterBuilder(name))
.type;
}
}
/**
* Registers the adapter's AngularJS upgrade module for unit testing in AngularJS.
* Use this instead of `angular.mock.module()` to load the upgrade module into
* the AngularJS testing injector.
*
* @usageNotes
* ### Example
*
* ```ts
* const upgradeAdapter = new UpgradeAdapter(MyNg2Module);
*
* // configure the adapter with upgrade/downgrade components and services
* upgradeAdapter.downgradeNg2Component(MyComponent);
*
* let upgradeAdapterRef: UpgradeAdapterRef;
* let $compile, $rootScope;
*
* // We must register the adapter before any calls to `inject()`
* beforeEach(() => {
* upgradeAdapterRef = upgradeAdapter.registerForNg1Tests(['heroApp']);
* });
*
* beforeEach(inject((_$compile_, _$rootScope_) => {
* $compile = _$compile_;
* $rootScope = _$rootScope_;
* }));
*
* it("says hello", (done) => {
* upgradeAdapterRef.ready(() => {
* const element = $compile("<my-component></my-component>")($rootScope);
* $rootScope.$apply();
* expect(element.html()).toContain("Hello World");
* done();
* })
* });
*
* ```
*
* @param modules any AngularJS modules that the upgrade module should depend upon
* @returns an `UpgradeAdapterRef`, which lets you register a `ready()` callback to
* run assertions once the Angular components are ready to test through AngularJS.
*/
registerForNg1Tests(modules) {
const windowNgMock = window['angular'].mock;
if (!windowNgMock || !windowNgMock.module) {
throw new Error("Failed to find 'angular.mock.module'.");
}
const { ng1Module, ng2BootstrapDeferred } = this.declareNg1Module(modules);
windowNgMock.module(ng1Module.name);
const upgrade = new UpgradeAdapterRef();
ng2BootstrapDeferred.promise.then((ng1Injector) => {
// @ts-expect-error
upgrade._bootstrapDone(this.moduleRef, ng1Injector);
}, onError);
return upgrade;
}
/**
* Bootstrap a hybrid AngularJS / Angular application.
*
* This `bootstrap` method is a direct replacement (takes same arguments) for AngularJS
* [`bootstrap`](https://docs.angularjs.org/api/ng/function/angular.bootstrap) method. Unlike
* AngularJS, this bootstrap is asynchronous.
*
* @usageNotes
* ### Example
*
* ```angular-ts
* const adapter = new UpgradeAdapter(MyNg2Module);
* const module = angular.module('myExample', []);
* module.directive('ng2', adapter.downgradeNg2Component(Ng2));
*
* module.directive('ng1', function() {
* return {
* scope: { title: '=' },
* template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
* };
* });
*
*
* @Component({
* selector: 'ng2',
* inputs: ['name'],
* template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)'
* })
* class Ng2 {
* }
*
* @NgModule({
* declarations: [Ng2, adapter.upgradeNg1Component('ng1')],
* imports: [BrowserModule]
* })
* class MyNg2Module {}
*
* document.body.innerHTML = '<ng2 name="World">project</ng2>';
*
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
* expect(document.body.textContent).toEqual(
* "ng2[ng1[Hello World!](transclude)](project)");
* });
* ```
*/
bootstrap(element$1, modules, config) {
const { ng1Module, ng2BootstrapDeferred, ngZone } = this.declareNg1Module(modules);
const upgrade = new UpgradeAdapterRef();
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
const windowAngular = window['angular'];
windowAngular.resumeBootstrap = undefined;
ngZone.run(() => {
bootstrap(element$1, [ng1Module.name], config);
});
const ng1BootstrapPromise = new Promise((resolve) => {
if (windowAngular.resumeBootstrap) {
const originalResumeBootstrap = windowAngular.resumeBootstrap;
windowAngular.resumeBootstrap = function () {
windowAngular.resumeBootstrap = originalResumeBootstrap;
const r = windowAngular.resumeBootstrap.apply(this, arguments);
resolve();
return r;
};
}
else {
resolve();
}
});
Promise.all([ng2BootstrapDeferred.promise, ng1BootstrapPromise]).then(([ng1Injector]) => {
element(element$1).data(controllerKey(INJECTOR_KEY), this.moduleRef.injector);
this.moduleRef.injector.get(NgZone).run(() => {
// @ts-expect-error
upgrade._bootstrapDone(this.moduleRef, ng1Injector);
});
}, onError);
return upgrade;
}
/**
* Allows AngularJS service to be accessible from Angular.
*
* @usageNotes
* ### Example
*
* ```ts
* class Login { ... }
* class Server { ... }
*
* @Injectable()
* class Example {
* constructor(@Inject('server') server, login: Login) {
* ...
* }
* }
*
* const module = angular.module('myExample', []);
* module.service('server', Server);
* module.service('login', Login);
*
* const adapter = new UpgradeAdapter(MyNg2Module);
* adapter.upgradeNg1Provider('server');
* adapter.upgradeNg1Provider('login', {asToken: Login});
*
* adapter.bootstrap(document.body, ['myExample']).ready((ref) => {
* const example: Example = ref.ng2Injector.get(Example);
* });
*
* ```
*/
upgradeNg1Provider(name, options) {
const token = (options && options.asToken) || name;
this.upgradedProviders.push({
provide: token,
useFactory: ($injector) => $injector.get(name),
deps: [$INJECTOR],
});
}
/**
* Allows Angular service to be accessible from AngularJS.
*
* @usageNotes
* ### Example
*
* ```ts
* class Example {
* }
*
* const adapter = new UpgradeAdapter(MyNg2Module);
*
* const module = angular.module('myExample', []);
* module.factory('example', adapter.downgradeNg2Provider(Example));
*
* adapter.bootstrap(document.body, ['myExample']).ready((ref) => {
* const example: Example = ref.ng1Injector.get('example');
* });
*
* ```
*/
downgradeNg2Provider(token) {
return downgradeInjectable(token);
}
/**
* Declare the AngularJS upgrade module for this adapter without bootstrapping the whole
* hybrid application.
*
* This method is automatically called by `bootstrap()` and `registerForNg1Tests()`.
*
* @param modules The AngularJS modules that this upgrade module should depend upon.
* @returns The AngularJS upgrade module that is declared by this method
*
* @usageNotes
* ### Example
*
* ```ts
* const upgradeAdapter = new UpgradeAdapter(MyNg2Module);
* upgradeAdapter.declareNg1Module(['heroApp']);
* ```
*/
declareNg1Module(modules = []) {
const delayApplyExps = [];
let original$applyFn;
let rootScopePrototype;
const upgradeAdapter = this;
const ng1Module = module_(this.idPrefix, modules);
const platformRef = platformBrowserDynamic();
const ngZone = new NgZone({
enableLongStackTrace: Zone.hasOwnProperty('longStackTraceZoneSpec'),
});
const ng2BootstrapDeferred = new Deferred();
ng1Module
.constant(UPGRADE_APP_TYPE_KEY, 1 /* UpgradeAppType.Dynamic */)
.factory(INJECTOR_KEY, () => this.moduleRef.injector.get(Injector))
.factory(LAZY_MODULE_REF, [
INJECTOR_KEY,
(injector) => ({ injector }),
])
.constant(NG_ZONE_KEY, ngZone)
.factory(COMPILER_KEY, () => this.moduleRef.injector.get(Compiler))
.config([
'$provide',
'$injector',
(provide, ng1Injector) => {
provide.decorator($ROOT_SCOPE, [
'$delegate',
function (rootScopeDelegate) {
// Capture the root apply so that we can delay first call to $apply until we
// bootstrap Angular and then we replay and restore the $apply.
rootScopePrototype = rootScopeDelegate.constructor.prototype;
if (rootScopePrototype.hasOwnProperty('$apply')) {
original$applyFn = rootScopePrototype.$apply;
rootScopePrototype.$apply = (exp) => delayApplyExps.push(exp);
}
else {
throw new Error("Failed to find '$apply' on '$rootScope'!");
}
return rootScopeDelegate;
},
]);
if (ng1Injector.has($$TESTABILITY)) {
provide.decorator($$TESTABILITY, [
'$delegate',
function (testabilityDelegate) {
const originalWhenStable = testabilityDelegate.whenStable;
// Cannot use arrow function below because we need the context
const newWhenStable = function (callback) {
originalWhenStable.call(this, function () {
const ng2Testability = upgradeAdapter.moduleRef.injector.get(Testability);
if (ng2Testability.isStable()) {
callback.apply(this, arguments);
}
else {
ng2Testability.whenStable(newWhenStable.bind(this, callback));
}
});
};
testabilityDelegate.whenStable = newWhenStable;
return testabilityDelegate;
},
]);
}
},
]);
ng1Module.run([
'$injector',
'$rootScope',
(ng1Injector, rootScope) => {
UpgradeNg1ComponentAdapterBuilder.resolve(this.ng1ComponentsToBeUpgraded, ng1Injector)
.then(() => {
// At this point we have ng1 injector and we have prepared
// ng1 components to be upgraded, we now can bootstrap ng2.
let DynamicNgUpgradeModule = class DynamicNgUpgradeModule {
ngDoBootstrap() { }
};
DynamicNgUpgradeModule = __decorate([
NgModule({
jit: true,
providers: [
{ provide: $INJECTOR, useFactory: () => ng1Injector },
{ provide: $COMPILE, useFactory: () => ng1Injector.get($COMPILE) },
this.upgradedProviders,
],
imports: [resolveForwardRef(this.ng2AppModule)],
})
], DynamicNgUpgradeModule);
platformRef
.bootstrapModule(DynamicNgUpgradeModule, [this.compilerOptions, { ngZone }])
.then((ref) => {
this.moduleRef = ref;
ngZone.run(() => {
if (rootScopePrototype) {
rootScopePrototype.$apply = original$applyFn; // restore original $apply
while (delayApplyExps.length) {
rootScope.$apply(delayApplyExps.shift());
}
rootScopePrototype = null;
}
});
})
.then(() => ng2BootstrapDeferred.resolve(ng1Injector), onError)
.then(() => {
let subscription = ngZone.onMicrotaskEmpty.subscribe({
next: () => {
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.');
}
return rootScope.$evalAsync(() => { });
}
return rootScope.$digest();
},
});
rootScope.$on('$destroy', () => {
subscription.unsubscribe();
});
// 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.
platformRef.onDestroy(() => destroyApp(ng1Injector));
});
})
.catch((e) => ng2BootstrapDeferred.reject(e));
},
]);
return { ng1Module, ng2BootstrapDeferred, ngZone };
}
}
/**
* Use `UpgradeAdapterRef` to control a hybrid AngularJS / Angular application.
*
* @deprecated Deprecated since v5. Use `upgrade/static` instead, which also supports
* [Ahead-of-Time compilation](tools/cli/aot-compiler).
* @publicApi
*/
class UpgradeAdapterRef {
/* @internal */
_readyFn = null;
ng1RootScope = null;
ng1Injector = null;
ng2ModuleRef = null;
ng2Injector = null;
/* @internal */
_bootstrapDone(ngModuleRef, ng1Injector) {
this.ng2ModuleRef = ngModuleRef;
this.ng2Injector = ngModuleRef.injector;
this.ng1Injector = ng1Injector;
this.ng1RootScope = ng1Injector.get($ROOT_SCOPE);
this._readyFn && this._readyFn(this);
}
/**
* Register a callback function which is notified upon successful hybrid AngularJS / Angular
* application has been bootstrapped.
*
* The `ready` callback function is invoked inside the Angular zone, therefore it does not
* require a call to `$apply()`.
*/
ready(fn) {
this._readyFn = fn;
}
/**
* Dispose of running hybrid AngularJS / Angular application.
*/
dispose() {
this.ng1Injector.get($ROOT_SCOPE).$destroy();
this.ng2ModuleRef.destroy();
}
}
export { UpgradeAdapter, UpgradeAdapterRef };
//# sourceMappingURL=upgrade.mjs.map