UNPKG

@angular/upgrade

Version:

Angular - the library for easing update from v1 to v2

887 lines (882 loc) 36.8 kB
/** * @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