UNPKG

@angular/upgrade

Version:

Angular - the library for easing update from v1 to v2

1,139 lines (1,126 loc) 86.2 kB
/** * @license Angular v11.2.4 * (c) 2010-2021 Google LLC. https://angular.io/ * License: MIT */ import { Injector, ChangeDetectorRef, Testability, TestabilityRegistry, ApplicationRef, SimpleChange, NgZone, ComponentFactoryResolver, Version, ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR, PlatformRef, EventEmitter, Directive, ElementRef, isDevMode, NgModule } from '@angular/core'; import { platformBrowser } from '@angular/platform-browser'; /** * @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 */ function noNg() { throw new Error('AngularJS v1.x is not loaded!'); } const noNgElement = (() => noNg()); noNgElement.cleanData = noNg; let angular = { bootstrap: noNg, module: noNg, element: noNgElement, injector: noNg, version: undefined, resumeBootstrap: noNg, getTestability: noNg }; try { if (window.hasOwnProperty('angular')) { angular = window.angular; } } catch (_a) { // ignore in CJS mode. } /** * @deprecated Use `setAngularJSGlobal` instead. * * @publicApi */ function setAngularLib(ng) { setAngularJSGlobal(ng); } /** * @deprecated Use `getAngularJSGlobal` instead. * * @publicApi */ function getAngularLib() { return getAngularJSGlobal(); } /** * Resets the AngularJS global. * * Used when AngularJS is loaded lazily, and not available on `window`. * * @publicApi */ function setAngularJSGlobal(ng) { angular = ng; } /** * Returns the current AngularJS global. * * @publicApi */ function getAngularJSGlobal() { return angular; } const bootstrap = (e, modules, config) => angular.bootstrap(e, modules, config); // Do not declare as `module` to avoid webpack bug // (see https://github.com/angular/angular/issues/30050). const module_ = (prefix, dependencies) => angular.module(prefix, dependencies); const element = (e => angular.element(e)); element.cleanData = nodes => angular.element.cleanData(nodes); const injector = (modules, strictDi) => angular.injector(modules, strictDi); const resumeBootstrap = () => angular.resumeBootstrap(); const getTestability = e => angular.getTestability(e); /** * @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 */ const $COMPILE = '$compile'; const $CONTROLLER = '$controller'; const $DELEGATE = '$delegate'; const $EXCEPTION_HANDLER = '$exceptionHandler'; const $HTTP_BACKEND = '$httpBackend'; const $INJECTOR = '$injector'; const $INTERVAL = '$interval'; const $PARSE = '$parse'; const $PROVIDE = '$provide'; const $ROOT_ELEMENT = '$rootElement'; const $ROOT_SCOPE = '$rootScope'; const $SCOPE = '$scope'; const $TEMPLATE_CACHE = '$templateCache'; const $TEMPLATE_REQUEST = '$templateRequest'; const $$TESTABILITY = '$$testability'; const COMPILER_KEY = '$$angularCompiler'; const DOWNGRADED_MODULE_COUNT_KEY = '$$angularDowngradedModuleCount'; const GROUP_PROJECTABLE_NODES_KEY = '$$angularGroupProjectableNodes'; const INJECTOR_KEY = '$$angularInjector'; const LAZY_MODULE_REF = '$$angularLazyModuleRef'; const NG_ZONE_KEY = '$$angularNgZone'; const UPGRADE_APP_TYPE_KEY = '$$angularUpgradeAppType'; const REQUIRE_INJECTOR = '?^^' + INJECTOR_KEY; const REQUIRE_NG_MODEL = '?ngModel'; const UPGRADE_MODULE_NAME = '$$UpgradeModule'; /** * @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 */ /** * A `PropertyBinding` represents a mapping between a property name * and an attribute name. It is parsed from a string of the form * `"prop: attr"`; or simply `"propAndAttr" where the property * and attribute have the same identifier. */ class PropertyBinding { constructor(prop, attr) { this.prop = prop; this.attr = attr; this.parseBinding(); } parseBinding() { this.bracketAttr = `[${this.attr}]`; this.parenAttr = `(${this.attr})`; this.bracketParenAttr = `[(${this.attr})]`; const capitalAttr = this.attr.charAt(0).toUpperCase() + this.attr.substr(1); this.onAttr = `on${capitalAttr}`; this.bindAttr = `bind${capitalAttr}`; this.bindonAttr = `bindon${capitalAttr}`; } } /** * @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 */ const DIRECTIVE_PREFIX_REGEXP = /^(?:x|data)[:\-_]/i; const DIRECTIVE_SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g; function onError(e) { // TODO: (misko): We seem to not have a stack trace here! if (console.error) { console.error(e, e.stack); } else { // tslint:disable-next-line:no-console console.log(e, e.stack); } throw e; } /** * Clean the jqLite/jQuery data on the element and all its descendants. * Equivalent to how jqLite/jQuery invoke `cleanData()` on an Element when removed: * https://github.com/angular/angular.js/blob/2e72ea13fa98bebf6ed4b5e3c45eaf5f990ed16f/src/jqLite.js#L349-L355 * https://github.com/jquery/jquery/blob/6984d1747623dbc5e87fd6c261a5b6b1628c107c/src/manipulation.js#L182 * * NOTE: * `cleanData()` will also invoke the AngularJS `$destroy` DOM event on the element: * https://github.com/angular/angular.js/blob/2e72ea13fa98bebf6ed4b5e3c45eaf5f990ed16f/src/Angular.js#L1932-L1945 * * @param node The DOM node whose data needs to be cleaned. */ function cleanData(node) { element.cleanData([node]); if (isParentNode(node)) { element.cleanData(node.querySelectorAll('*')); } } function controllerKey(name) { return '$' + name + 'Controller'; } /** * Destroy an AngularJS app given the app `$injector`. * * NOTE: Destroying an app is not officially supported by AngularJS, but try to do our best by * destroying `$rootScope` and clean the jqLite/jQuery data on `$rootElement` and all * descendants. * * @param $injector The `$injector` of the AngularJS app to destroy. */ function destroyApp($injector) { const $rootElement = $injector.get($ROOT_ELEMENT); const $rootScope = $injector.get($ROOT_SCOPE); $rootScope.$destroy(); cleanData($rootElement[0]); } function directiveNormalize(name) { return name.replace(DIRECTIVE_PREFIX_REGEXP, '') .replace(DIRECTIVE_SPECIAL_CHARS_REGEXP, (_, letter) => letter.toUpperCase()); } function getTypeName(type) { // Return the name of the type or the first line of its stringified version. return type.overriddenName || type.name || type.toString().split('\n')[0]; } function getDowngradedModuleCount($injector) { return $injector.has(DOWNGRADED_MODULE_COUNT_KEY) ? $injector.get(DOWNGRADED_MODULE_COUNT_KEY) : 0; } function getUpgradeAppType($injector) { return $injector.has(UPGRADE_APP_TYPE_KEY) ? $injector.get(UPGRADE_APP_TYPE_KEY) : 0 /* None */; } function isFunction(value) { return typeof value === 'function'; } function isParentNode(node) { return isFunction(node.querySelectorAll); } function validateInjectionKey($injector, downgradedModule, injectionKey, attemptedAction) { const upgradeAppType = getUpgradeAppType($injector); const downgradedModuleCount = getDowngradedModuleCount($injector); // Check for common errors. switch (upgradeAppType) { case 1 /* Dynamic */: case 2 /* Static */: if (downgradedModule) { throw new Error(`Error while ${attemptedAction}: 'downgradedModule' unexpectedly specified.\n` + 'You should not specify a value for \'downgradedModule\', unless you are downgrading ' + 'more than one Angular module (via \'downgradeModule()\').'); } break; case 3 /* Lite */: if (!downgradedModule && (downgradedModuleCount >= 2)) { throw new Error(`Error while ${attemptedAction}: 'downgradedModule' not specified.\n` + 'This application contains more than one downgraded Angular module, thus you need to ' + 'always specify \'downgradedModule\' when downgrading components and injectables.'); } if (!$injector.has(injectionKey)) { throw new Error(`Error while ${attemptedAction}: Unable to find the specified downgraded module.\n` + 'Did you forget to downgrade an Angular module or include it in the AngularJS ' + 'application?'); } break; default: throw new Error(`Error while ${attemptedAction}: Not a valid '@angular/upgrade' application.\n` + 'Did you forget to downgrade an Angular module or include it in the AngularJS ' + 'application?'); } } class Deferred { constructor() { this.promise = new Promise((res, rej) => { this.resolve = res; this.reject = rej; }); } } /** * @return Whether the passed-in component implements the subset of the * `ControlValueAccessor` interface needed for AngularJS `ng-model` * compatibility. */ function supportsNgModel(component) { return typeof component.writeValue === 'function' && typeof component.registerOnChange === 'function'; } /** * Glue the AngularJS `NgModelController` (if it exists) to the component * (if it implements the needed subset of the `ControlValueAccessor` interface). */ function hookupNgModel(ngModel, component) { if (ngModel && supportsNgModel(component)) { ngModel.$render = () => { component.writeValue(ngModel.$viewValue); }; component.registerOnChange(ngModel.$setViewValue.bind(ngModel)); if (typeof component.registerOnTouched === 'function') { component.registerOnTouched(ngModel.$setTouched.bind(ngModel)); } } } /** * Test two values for strict equality, accounting for the fact that `NaN !== NaN`. */ function strictEquals(val1, val2) { return val1 === val2 || (val1 !== val1 && val2 !== val2); } /** * @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 */ const INITIAL_VALUE = { __UNINITIALIZED__: true }; class DowngradeComponentAdapter { constructor(element, attrs, scope, ngModel, parentInjector, $compile, $parse, componentFactory, wrapCallback) { this.element = element; this.attrs = attrs; this.scope = scope; this.ngModel = ngModel; this.parentInjector = parentInjector; this.$compile = $compile; this.$parse = $parse; this.componentFactory = componentFactory; this.wrapCallback = wrapCallback; this.implementsOnChanges = false; this.inputChangeCount = 0; this.inputChanges = {}; this.componentScope = scope.$new(); } compileContents() { const compiledProjectableNodes = []; const projectableNodes = this.groupProjectableNodes(); const linkFns = projectableNodes.map(nodes => this.$compile(nodes)); this.element.empty(); linkFns.forEach(linkFn => { linkFn(this.scope, (clone) => { compiledProjectableNodes.push(clone); this.element.append(clone); }); }); return compiledProjectableNodes; } createComponent(projectableNodes) { const providers = [{ provide: $SCOPE, useValue: this.componentScope }]; const childInjector = Injector.create({ providers: providers, parent: this.parentInjector, name: 'DowngradeComponentAdapter' }); this.componentRef = this.componentFactory.create(childInjector, projectableNodes, this.element[0]); this.viewChangeDetector = this.componentRef.injector.get(ChangeDetectorRef); this.changeDetector = this.componentRef.changeDetectorRef; this.component = this.componentRef.instance; // testability hook is commonly added during component bootstrap in // packages/core/src/application_ref.bootstrap() // in downgraded application, component creation will take place here as well as adding the // testability hook. const testability = this.componentRef.injector.get(Testability, null); if (testability) { this.componentRef.injector.get(TestabilityRegistry) .registerApplication(this.componentRef.location.nativeElement, testability); } hookupNgModel(this.ngModel, this.component); } setupInputs(manuallyAttachView, propagateDigest = true) { const attrs = this.attrs; const inputs = this.componentFactory.inputs || []; for (let i = 0; i < inputs.length; i++) { const input = new PropertyBinding(inputs[i].propName, inputs[i].templateName); let expr = null; if (attrs.hasOwnProperty(input.attr)) { const observeFn = (prop => { let prevValue = INITIAL_VALUE; return (currValue) => { // Initially, both `$observe()` and `$watch()` will call this function. if (!strictEquals(prevValue, currValue)) { if (prevValue === INITIAL_VALUE) { prevValue = currValue; } this.updateInput(prop, prevValue, currValue); prevValue = currValue; } }; })(input.prop); attrs.$observe(input.attr, observeFn); // Use `$watch()` (in addition to `$observe()`) in order to initialize the input in time // for `ngOnChanges()`. This is necessary if we are already in a `$digest`, which means that // `ngOnChanges()` (which is called by a watcher) will run before the `$observe()` callback. let unwatch = this.componentScope.$watch(() => { unwatch(); unwatch = null; observeFn(attrs[input.attr]); }); } else if (attrs.hasOwnProperty(input.bindAttr)) { expr = attrs[input.bindAttr]; } else if (attrs.hasOwnProperty(input.bracketAttr)) { expr = attrs[input.bracketAttr]; } else if (attrs.hasOwnProperty(input.bindonAttr)) { expr = attrs[input.bindonAttr]; } else if (attrs.hasOwnProperty(input.bracketParenAttr)) { expr = attrs[input.bracketParenAttr]; } if (expr != null) { const watchFn = (prop => (currValue, prevValue) => this.updateInput(prop, prevValue, currValue))(input.prop); this.componentScope.$watch(expr, watchFn); } } // Invoke `ngOnChanges()` and Change Detection (when necessary) const detectChanges = () => this.changeDetector.detectChanges(); const prototype = this.componentFactory.componentType.prototype; this.implementsOnChanges = !!(prototype && prototype.ngOnChanges); this.componentScope.$watch(() => this.inputChangeCount, this.wrapCallback(() => { // Invoke `ngOnChanges()` if (this.implementsOnChanges) { const inputChanges = this.inputChanges; this.inputChanges = {}; this.component.ngOnChanges(inputChanges); } this.viewChangeDetector.markForCheck(); // If opted out of propagating digests, invoke change detection when inputs change. if (!propagateDigest) { detectChanges(); } })); // If not opted out of propagating digests, invoke change detection on every digest if (propagateDigest) { this.componentScope.$watch(this.wrapCallback(detectChanges)); } // If necessary, attach the view so that it will be dirty-checked. // (Allow time for the initial input values to be set and `ngOnChanges()` to be called.) if (manuallyAttachView || !propagateDigest) { let unwatch = this.componentScope.$watch(() => { unwatch(); unwatch = null; const appRef = this.parentInjector.get(ApplicationRef); appRef.attachView(this.componentRef.hostView); }); } } setupOutputs() { const attrs = this.attrs; const outputs = this.componentFactory.outputs || []; for (let j = 0; j < outputs.length; j++) { const output = new PropertyBinding(outputs[j].propName, outputs[j].templateName); const bindonAttr = output.bindonAttr.substring(0, output.bindonAttr.length - 6); const bracketParenAttr = `[(${output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8)})]`; // order below is important - first update bindings then evaluate expressions if (attrs.hasOwnProperty(bindonAttr)) { this.subscribeToOutput(output, attrs[bindonAttr], true); } if (attrs.hasOwnProperty(bracketParenAttr)) { this.subscribeToOutput(output, attrs[bracketParenAttr], true); } if (attrs.hasOwnProperty(output.onAttr)) { this.subscribeToOutput(output, attrs[output.onAttr]); } if (attrs.hasOwnProperty(output.parenAttr)) { this.subscribeToOutput(output, attrs[output.parenAttr]); } } } subscribeToOutput(output, expr, isAssignment = false) { const getter = this.$parse(expr); const setter = getter.assign; if (isAssignment && !setter) { throw new Error(`Expression '${expr}' is not assignable!`); } const emitter = this.component[output.prop]; if (emitter) { emitter.subscribe({ next: isAssignment ? (v) => setter(this.scope, v) : (v) => getter(this.scope, { '$event': v }) }); } else { throw new Error(`Missing emitter '${output.prop}' on component '${getTypeName(this.componentFactory.componentType)}'!`); } } registerCleanup() { const testabilityRegistry = this.componentRef.injector.get(TestabilityRegistry); const destroyComponentRef = this.wrapCallback(() => this.componentRef.destroy()); let destroyed = false; this.element.on('$destroy', () => { // The `$destroy` event may have been triggered by the `cleanData()` call in the // `componentScope` `$destroy` handler below. In that case, we don't want to call // `componentScope.$destroy()` again. if (!destroyed) this.componentScope.$destroy(); }); this.componentScope.$on('$destroy', () => { if (!destroyed) { destroyed = true; testabilityRegistry.unregisterApplication(this.componentRef.location.nativeElement); // The `componentScope` might be getting destroyed, because an ancestor element is being // removed/destroyed. If that is the case, jqLite/jQuery would normally invoke `cleanData()` // on the removed element and all descendants. // https://github.com/angular/angular.js/blob/2e72ea13fa98bebf6ed4b5e3c45eaf5f990ed16f/src/jqLite.js#L349-L355 // https://github.com/jquery/jquery/blob/6984d1747623dbc5e87fd6c261a5b6b1628c107c/src/manipulation.js#L182 // // Here, however, `destroyComponentRef()` may under some circumstances remove the element // from the DOM and therefore it will no longer be a descendant of the removed element when // `cleanData()` is called. This would result in a memory leak, because the element's data // and event handlers (and all objects directly or indirectly referenced by them) would be // retained. // // To ensure the element is always properly cleaned up, we manually call `cleanData()` on // this element and its descendants before destroying the `ComponentRef`. cleanData(this.element[0]); destroyComponentRef(); } }); } getInjector() { return this.componentRef.injector; } updateInput(prop, prevValue, currValue) { if (this.implementsOnChanges) { this.inputChanges[prop] = new SimpleChange(prevValue, currValue, prevValue === currValue); } this.inputChangeCount++; this.component[prop] = currValue; } groupProjectableNodes() { let ngContentSelectors = this.componentFactory.ngContentSelectors; return groupNodesBySelector(ngContentSelectors, this.element.contents()); } } /** * Group a set of DOM nodes into `ngContent` groups, based on the given content selectors. */ function groupNodesBySelector(ngContentSelectors, nodes) { const projectableNodes = []; for (let i = 0, ii = ngContentSelectors.length; i < ii; ++i) { projectableNodes[i] = []; } for (let j = 0, jj = nodes.length; j < jj; ++j) { const node = nodes[j]; const ngContentIndex = findMatchingNgContentIndex(node, ngContentSelectors); if (ngContentIndex != null) { projectableNodes[ngContentIndex].push(node); } } return projectableNodes; } function findMatchingNgContentIndex(element, ngContentSelectors) { const ngContentIndices = []; let wildcardNgContentIndex = -1; for (let i = 0; i < ngContentSelectors.length; i++) { const selector = ngContentSelectors[i]; if (selector === '*') { wildcardNgContentIndex = i; } else { if (matchesSelector(element, selector)) { ngContentIndices.push(i); } } } ngContentIndices.sort(); if (wildcardNgContentIndex !== -1) { ngContentIndices.push(wildcardNgContentIndex); } return ngContentIndices.length ? ngContentIndices[0] : null; } let _matches; function matchesSelector(el, selector) { if (!_matches) { const elProto = Element.prototype; _matches = elProto.matches || elProto.matchesSelector || elProto.mozMatchesSelector || elProto.msMatchesSelector || elProto.oMatchesSelector || elProto.webkitMatchesSelector; } return el.nodeType === Node.ELEMENT_NODE ? _matches.call(el, selector) : false; } /** * @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 */ function isThenable(obj) { return !!obj && isFunction(obj.then); } /** * Synchronous, promise-like object. */ class SyncPromise { constructor() { this.resolved = false; this.callbacks = []; } static all(valuesOrPromises) { const aggrPromise = new SyncPromise(); let resolvedCount = 0; const results = []; const resolve = (idx, value) => { results[idx] = value; if (++resolvedCount === valuesOrPromises.length) aggrPromise.resolve(results); }; valuesOrPromises.forEach((p, idx) => { if (isThenable(p)) { p.then(v => resolve(idx, v)); } else { resolve(idx, p); } }); return aggrPromise; } resolve(value) { // Do nothing, if already resolved. if (this.resolved) return; this.value = value; this.resolved = true; // Run the queued callbacks. this.callbacks.forEach(callback => callback(value)); this.callbacks.length = 0; } then(callback) { if (this.resolved) { callback(this.value); } else { this.callbacks.push(callback); } } } /** * @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 */ /** * @description * * A helper function that allows an Angular component to be used from AngularJS. * * *Part of the [upgrade/static](api?query=upgrade%2Fstatic) * library for hybrid upgrade apps that support AOT compilation* * * This helper function returns a factory function to be used for registering * an AngularJS wrapper directive for "downgrading" an Angular component. * * @usageNotes * ### Examples * * Let's assume that you have an Angular component called `ng2Heroes` that needs * to be made available in AngularJS templates. * * {@example upgrade/static/ts/full/module.ts region="ng2-heroes"} * * We must create an AngularJS [directive](https://docs.angularjs.org/guide/directive) * that will make this Angular component available inside AngularJS templates. * The `downgradeComponent()` function returns a factory function that we * can use to define the AngularJS directive that wraps the "downgraded" component. * * {@example upgrade/static/ts/full/module.ts region="ng2-heroes-wrapper"} * * For more details and examples on downgrading Angular components to AngularJS components please * visit the [Upgrade guide](guide/upgrade#using-angular-components-from-angularjs-code). * * @param info contains information about the Component that is being downgraded: * * - `component: Type<any>`: The type of the Component that will be downgraded * - `downgradedModule?: string`: The name of the downgraded module (if any) that the component * "belongs to", as returned by a call to `downgradeModule()`. It is the module, whose * corresponding Angular module will be bootstrapped, when the component needs to be instantiated. * <br /> * (This option is only necessary when using `downgradeModule()` to downgrade more than one * Angular module.) * - `propagateDigest?: boolean`: Whether to perform {@link ChangeDetectorRef#detectChanges * change detection} on the component on every * [$digest](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest). If set to `false`, * change detection will still be performed when any of the component's inputs changes. * (Default: true) * * @returns a factory function that can be used to register the component in an * AngularJS module. * * @publicApi */ function downgradeComponent(info) { const directiveFactory = function ($compile, $injector, $parse) { // When using `downgradeModule()`, we need to handle certain things specially. For example: // - We always need to attach the component view to the `ApplicationRef` for it to be // dirty-checked. // - We need to ensure callbacks to Angular APIs (e.g. change detection) are run inside the // Angular zone. // NOTE: This is not needed, when using `UpgradeModule`, because `$digest()` will be run // inside the Angular zone (except if explicitly escaped, in which case we shouldn't // force it back in). const isNgUpgradeLite = getUpgradeAppType($injector) === 3 /* Lite */; const wrapCallback = !isNgUpgradeLite ? cb => cb : cb => () => NgZone.isInAngularZone() ? cb() : ngZone.run(cb); let ngZone; // When downgrading multiple modules, special handling is needed wrt injectors. const hasMultipleDowngradedModules = isNgUpgradeLite && (getDowngradedModuleCount($injector) > 1); return { restrict: 'E', terminal: true, require: [REQUIRE_INJECTOR, REQUIRE_NG_MODEL], link: (scope, element, attrs, required) => { // We might have to compile the contents asynchronously, because this might have been // triggered by `UpgradeNg1ComponentAdapterBuilder`, before the Angular templates have // been compiled. const ngModel = required[1]; const parentInjector = required[0]; let moduleInjector = undefined; let ranAsync = false; if (!parentInjector || hasMultipleDowngradedModules) { const downgradedModule = info.downgradedModule || ''; const lazyModuleRefKey = `${LAZY_MODULE_REF}${downgradedModule}`; const attemptedAction = `instantiating component '${getTypeName(info.component)}'`; validateInjectionKey($injector, downgradedModule, lazyModuleRefKey, attemptedAction); const lazyModuleRef = $injector.get(lazyModuleRefKey); moduleInjector = lazyModuleRef.injector || lazyModuleRef.promise; } // Notes: // // There are two injectors: `finalModuleInjector` and `finalParentInjector` (they might be // the same instance, but that is irrelevant): // - `finalModuleInjector` is used to retrieve `ComponentFactoryResolver`, thus it must be // on the same tree as the `NgModule` that declares this downgraded component. // - `finalParentInjector` is used for all other injection purposes. // (Note that Angular knows to only traverse the component-tree part of that injector, // when looking for an injectable and then switch to the module injector.) // // There are basically three cases: // - If there is no parent component (thus no `parentInjector`), we bootstrap the downgraded // `NgModule` and use its injector as both `finalModuleInjector` and // `finalParentInjector`. // - If there is a parent component (and thus a `parentInjector`) and we are sure that it // belongs to the same `NgModule` as this downgraded component (e.g. because there is only // one downgraded module, we use that `parentInjector` as both `finalModuleInjector` and // `finalParentInjector`. // - If there is a parent component, but it may belong to a different `NgModule`, then we // use the `parentInjector` as `finalParentInjector` and this downgraded component's // declaring `NgModule`'s injector as `finalModuleInjector`. // Note 1: If the `NgModule` is already bootstrapped, we just get its injector (we don't // bootstrap again). // Note 2: It is possible that (while there are multiple downgraded modules) this // downgraded component and its parent component both belong to the same NgModule. // In that case, we could have used the `parentInjector` as both // `finalModuleInjector` and `finalParentInjector`, but (for simplicity) we are // treating this case as if they belong to different `NgModule`s. That doesn't // really affect anything, since `parentInjector` has `moduleInjector` as ancestor // and trying to resolve `ComponentFactoryResolver` from either one will return // the same instance. // If there is a parent component, use its injector as parent injector. // If this is a "top-level" Angular component, use the module injector. const finalParentInjector = parentInjector || moduleInjector; // If this is a "top-level" Angular component or the parent component may belong to a // different `NgModule`, use the module injector for module-specific dependencies. // If there is a parent component that belongs to the same `NgModule`, use its injector. const finalModuleInjector = moduleInjector || parentInjector; const doDowngrade = (injector, moduleInjector) => { // Retrieve `ComponentFactoryResolver` from the injector tied to the `NgModule` this // component belongs to. const componentFactoryResolver = moduleInjector.get(ComponentFactoryResolver); const componentFactory = componentFactoryResolver.resolveComponentFactory(info.component); if (!componentFactory) { throw new Error(`Expecting ComponentFactory for: ${getTypeName(info.component)}`); } const injectorPromise = new ParentInjectorPromise(element); const facade = new DowngradeComponentAdapter(element, attrs, scope, ngModel, injector, $compile, $parse, componentFactory, wrapCallback); const projectableNodes = facade.compileContents(); facade.createComponent(projectableNodes); facade.setupInputs(isNgUpgradeLite, info.propagateDigest); facade.setupOutputs(); facade.registerCleanup(); injectorPromise.resolve(facade.getInjector()); if (ranAsync) { // If this is run async, it is possible that it is not run inside a // digest and initial input values will not be detected. scope.$evalAsync(() => { }); } }; const downgradeFn = !isNgUpgradeLite ? doDowngrade : (pInjector, mInjector) => { if (!ngZone) { ngZone = pInjector.get(NgZone); } wrapCallback(() => doDowngrade(pInjector, mInjector))(); }; // NOTE: // Not using `ParentInjectorPromise.all()` (which is inherited from `SyncPromise`), because // Closure Compiler (or some related tool) complains: // `TypeError: ...$src$downgrade_component_ParentInjectorPromise.all is not a function` SyncPromise.all([finalParentInjector, finalModuleInjector]) .then(([pInjector, mInjector]) => downgradeFn(pInjector, mInjector)); ranAsync = true; } }; }; // bracket-notation because of closure - see #14441 directiveFactory['$inject'] = [$COMPILE, $INJECTOR, $PARSE]; return directiveFactory; } /** * Synchronous promise-like object to wrap parent injectors, * to preserve the synchronous nature of AngularJS's `$compile`. */ class ParentInjectorPromise extends SyncPromise { constructor(element) { super(); this.element = element; this.injectorKey = controllerKey(INJECTOR_KEY); // Store the promise on the element. element.data(this.injectorKey, this); } resolve(injector) { // Store the real injector on the element. this.element.data(this.injectorKey, injector); // Release the element to prevent memory leaks. this.element = null; // Resolve the promise. super.resolve(injector); } } /** * @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 */ /** * @description * * A helper function to allow an Angular service to be accessible from AngularJS. * * *Part of the [upgrade/static](api?query=upgrade%2Fstatic) * library for hybrid upgrade apps that support AOT compilation* * * This helper function returns a factory function that provides access to the Angular * service identified by the `token` parameter. * * @usageNotes * ### Examples * * First ensure that the service to be downgraded is provided in an `NgModule` * that will be part of the upgrade application. For example, let's assume we have * defined `HeroesService` * * {@example upgrade/static/ts/full/module.ts region="ng2-heroes-service"} * * and that we have included this in our upgrade app `NgModule` * * {@example upgrade/static/ts/full/module.ts region="ng2-module"} * * Now we can register the `downgradeInjectable` factory function for the service * on an AngularJS module. * * {@example upgrade/static/ts/full/module.ts region="downgrade-ng2-heroes-service"} * * Inside an AngularJS component's controller we can get hold of the * downgraded service via the name we gave when downgrading. * * {@example upgrade/static/ts/full/module.ts region="example-app"} * * <div class="alert is-important"> * * When using `downgradeModule()`, downgraded injectables will not be available until the Angular * module that provides them is instantiated. In order to be safe, you need to ensure that the * downgraded injectables are not used anywhere _outside_ the part of the app where it is * guaranteed that their module has been instantiated. * * For example, it is _OK_ to use a downgraded service in an upgraded component that is only used * from a downgraded Angular component provided by the same Angular module as the injectable, but * it is _not OK_ to use it in an AngularJS component that may be used independently of Angular or * use it in a downgraded Angular component from a different module. * * </div> * * @param token an `InjectionToken` that identifies a service provided from Angular. * @param downgradedModule the name of the downgraded module (if any) that the injectable * "belongs to", as returned by a call to `downgradeModule()`. It is the module, whose injector will * be used for instantiating the injectable.<br /> * (This option is only necessary when using `downgradeModule()` to downgrade more than one Angular * module.) * * @returns a [factory function](https://docs.angularjs.org/guide/di) that can be * used to register the service on an AngularJS module. * * @publicApi */ function downgradeInjectable(token, downgradedModule = '') { const factory = function ($injector) { const injectorKey = `${INJECTOR_KEY}${downgradedModule}`; const injectableName = isFunction(token) ? getTypeName(token) : String(token); const attemptedAction = `instantiating injectable '${injectableName}'`; validateInjectionKey($injector, downgradedModule, injectorKey, attemptedAction); try { const injector = $injector.get(injectorKey); return injector.get(token); } catch (err) { throw new Error(`Error while ${attemptedAction}: ${err.message || err}`); } }; factory['$inject'] = [$INJECTOR]; return factory; } /** * @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 */ /** * @publicApi */ const VERSION = new Version('11.2.4'); /** * @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 */ // 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'] } ]; /** * @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 */ class NgAdapterInjector { 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); } } /** * @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 */ 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` or a function: * - `NgModuleFactory`: If you pass an `NgModuleFactory`, it will be used to instantiate a module * using `platformBrowser`'s {@link PlatformRef#bootstrapModuleFactory bootstrapModuleFactory()}. * - `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](guide/upgrade-performance). * * @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="alert is-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](guide/upgrade-performance). * * <div class="alert is-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 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(moduleFactoryOrBootstrapFn) { const lazyModuleName = `${UPGRADE_MODULE_NAME}.lazy${++moduleUid}`; const lazyModuleRefKey = `${LAZY_MODULE_REF}${lazyModuleName}`; const lazyInjectorKey = `${INJECTOR_KEY}${lazyModuleName}`; const bootstrapFn = isFunction(moduleFactoryOrBootstrapFn) ? moduleFactoryOrBootstrapFn : (extraProviders) => platformBrowser(extraProviders).bootstrapModuleFactory(moduleFactoryOrBootstrapFn); let injector; // Create an ng1 module to bootstrap. module_(lazyModuleName, []) .constant(UPGRADE_APP_TYPE_KEY, 3 /* 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) => { $pr