@angular/upgrade
Version:
Angular - the library for easing update from v1 to v2
1,139 lines (1,126 loc) • 86.2 kB
JavaScript
/**
* @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