angular/packages/upgrade/src/static/upgrade_module.ts
Peter Bacon Darwin dbffdcc442 docs(upgrade): fix sub-ordered-list syntax (#18487)
We must always use 1., 2. etc, to indicate ordered lists, even for sub-lists.
We can change the sublist to display as a., b. etc, via CSS.

PR Close #18487
2018-06-27 14:53:24 -07:00

287 lines
13 KiB
TypeScript

/**
* @license
* Copyright Google Inc. 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
*/
import {Injector, NgModule, NgZone, Testability} from '@angular/core';
import * as angular from '../common/angular1';
import {$$TESTABILITY, $DELEGATE, $INJECTOR, $INTERVAL, $PROVIDE, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_MODULE_NAME} from '../common/constants';
import {LazyModuleRef, controllerKey} from '../common/util';
import {angular1Providers, setTempInjectorRef} from './angular1_providers';
import {NgAdapterInjector} from './util';
/**
* @description
*
* An `NgModule`, which you import to provide AngularJS core services,
* and has an instance method used to bootstrap the hybrid upgrade application.
*
* *Part of the [upgrade/static](api?query=upgrade/static)
* library for hybrid upgrade apps that support AoT compilation*
*
* The `upgrade/static` package contains helpers that allow AngularJS and Angular components
* to be used together inside a hybrid upgrade application, which supports AoT compilation.
*
* Specifically, the classes and functions in the `upgrade/static` module allow the following:
*
* 1. Creation of an Angular directive that wraps and exposes an AngularJS component so
* that it can be used in an Angular template. See `UpgradeComponent`.
* 2. Creation of an AngularJS directive that wraps and exposes an Angular component so
* that it can be used in an AngularJS template. See `downgradeComponent`.
* 3. Creation of an Angular root injector provider that wraps and exposes an AngularJS
* service so that it can be injected into an Angular context. See
* {@link UpgradeModule#upgrading-an-angular-1-service Upgrading an AngularJS service} below.
* 4. Creation of an AngularJS service that wraps and exposes an Angular injectable
* so that it can be injected into an AngularJS context. See `downgradeInjectable`.
* 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks
* coexisting in a single application.
*
* @usageNotes
*
* ```ts
* import {UpgradeModule} from '@angular/upgrade/static';
* ```
*
* See also the {@link UpgradeModule#examples examples} below.
*
* ### Mental Model
*
* When reasoning about how a hybrid application works it is useful to have a mental model which
* describes what is happening and explains what is happening at the lowest level.
*
* 1. There are two independent frameworks running in a single application, each framework treats
* the other as a black box.
* 2. Each DOM element on the page is owned exactly by one framework. Whichever framework
* instantiated the element is the owner. Each framework only updates/interacts with its own
* DOM elements and ignores others.
* 3. AngularJS directives always execute inside the AngularJS framework codebase regardless of
* where they are instantiated.
* 4. Angular components always execute inside the Angular framework codebase regardless of
* where they are instantiated.
* 5. An AngularJS component can be "upgraded"" to an Angular component. This is achieved by
* defining an Angular directive, which bootstraps the AngularJS component at its location
* in the DOM. See `UpgradeComponent`.
* 6. An Angular component can be "downgraded" to an AngularJS component. This is achieved by
* defining an AngularJS directive, which bootstraps the Angular component at its location
* in the DOM. See `downgradeComponent`.
* 7. Whenever an "upgraded"/"downgraded" component is instantiated the host element is owned by
* the framework doing the instantiation. The other framework then instantiates and owns the
* view for that component.
* 1. This implies that the component bindings will always follow the semantics of the
* instantiation framework.
* 2. The DOM attributes are parsed by the framework that owns the current template. So
* attributes in AngularJS templates must use kebab-case, while AngularJS templates must use
* camelCase.
* 3. However the template binding syntax will always use the Angular style, e.g. square
* brackets (`[...]`) for property binding.
* 8. Angular is bootstrapped first; AngularJS is bootstrapped second. AngularJS always owns the
* root component of the application.
* 9. The new application is running in an Angular zone, and therefore it no longer needs calls to
* `$apply()`.
*
* ### The `UpgradeModule` class
*
* This class is an `NgModule`, which you import to provide AngularJS core services,
* and has an instance method used to bootstrap the hybrid upgrade application.
*
* #### Core AngularJS services
* Importing this `NgModule` will add providers for the core
* [AngularJS services](https://docs.angularjs.org/api/ng/service) to the root injector.
*
* #### Bootstrap
* The runtime instance of this class contains a {@link UpgradeModule#bootstrap `bootstrap()`}
* method, which you use to bootstrap the top level AngularJS module onto an element in the
* DOM for the hybrid upgrade app.
*
* It also contains properties to access the {@link UpgradeModule#injector root injector}, the
* bootstrap `NgZone` and the
* [AngularJS $injector](https://docs.angularjs.org/api/auto/service/$injector).
*
* ### Examples
*
* Import the `UpgradeModule` into your top level {@link NgModule Angular `NgModule`}.
*
* {@example upgrade/static/ts/full/module.ts region='ng2-module'}
*
* Then inject `UpgradeModule` into your Angular `NgModule` and use it to bootstrap the top level
* [AngularJS module](https://docs.angularjs.org/api/ng/type/angular.Module) in the
* `ngDoBootstrap()` method.
*
* {@example upgrade/static/ts/full/module.ts region='bootstrap-ng1'}
*
* Finally, kick off the whole process, by bootstraping your top level Angular `NgModule`.
*
* {@example upgrade/static/ts/full/module.ts region='bootstrap-ng2'}
*
* {@a upgrading-an-angular-1-service}
* ### Upgrading an AngularJS service
*
* There is no specific API for upgrading an AngularJS service. Instead you should just follow the
* following recipe:
*
* Let's say you have an AngularJS service:
*
* {@example upgrade/static/ts/full/module.ts region="ng1-title-case-service"}
*
* Then you should define an Angular provider to be included in your `NgModule` `providers`
* property.
*
* {@example upgrade/static/ts/full/module.ts region="upgrade-ng1-service"}
*
* Then you can use the "upgraded" AngularJS service by injecting it into an Angular component
* or service.
*
* {@example upgrade/static/ts/full/module.ts region="use-ng1-upgraded-service"}
*
* @experimental
*/
@NgModule({providers: [angular1Providers]})
export class UpgradeModule {
/**
* The AngularJS `$injector` for the upgrade application.
*/
public $injector: any /*angular.IInjectorService*/;
/** The Angular Injector **/
public injector: Injector;
constructor(
/** The root `Injector` for the upgrade application. */
injector: Injector,
/** The bootstrap zone for the upgrade application */
public ngZone: NgZone) {
this.injector = new NgAdapterInjector(injector);
}
/**
* Bootstrap an AngularJS application from this NgModule
* @param element the element on which to bootstrap the AngularJS application
* @param [modules] the AngularJS modules to bootstrap for this application
* @param [config] optional extra AngularJS bootstrap configuration
*/
bootstrap(
element: Element, modules: string[] = [], config?: any /*angular.IAngularBootstrapConfig*/) {
const INIT_MODULE_NAME = UPGRADE_MODULE_NAME + '.init';
// Create an ng1 module to bootstrap
const initModule =
angular
.module(INIT_MODULE_NAME, [])
.value(INJECTOR_KEY, this.injector)
.factory(
LAZY_MODULE_REF,
[
INJECTOR_KEY,
(injector: Injector) => ({ injector, needsNgZone: false } as LazyModuleRef)
])
.config([
$PROVIDE, $INJECTOR,
($provide: angular.IProvideService, $injector: angular.IInjectorService) => {
if ($injector.has($$TESTABILITY)) {
$provide.decorator($$TESTABILITY, [
$DELEGATE,
(testabilityDelegate: angular.ITestabilityService) => {
const originalWhenStable: Function = testabilityDelegate.whenStable;
const injector = this.injector;
// Cannot use arrow function below because we need the context
const newWhenStable = function(callback: Function) {
originalWhenStable.call(testabilityDelegate, function() {
const ng2Testability: Testability = injector.get(Testability);
if (ng2Testability.isStable()) {
callback();
} else {
ng2Testability.whenStable(
newWhenStable.bind(testabilityDelegate, callback));
}
});
};
testabilityDelegate.whenStable = newWhenStable;
return testabilityDelegate;
}
]);
}
if ($injector.has($INTERVAL)) {
$provide.decorator($INTERVAL, [
$DELEGATE,
(intervalDelegate: angular.IIntervalService) => {
// Wrap the $interval service so that setInterval is called outside NgZone,
// but the callback is still invoked within it. This is so that $interval
// won't block stability, which preserves the behavior from AngularJS.
let wrappedInterval =
(fn: Function, delay: number, count?: number, invokeApply?: boolean,
...pass: any[]) => {
return this.ngZone.runOutsideAngular(() => {
return intervalDelegate((...args: any[]) => {
// Run callback in the next VM turn - $interval calls
// $rootScope.$apply, and running the callback in NgZone will
// cause a '$digest already in progress' error if it's in the
// same vm turn.
setTimeout(() => { this.ngZone.run(() => fn(...args)); });
}, delay, count, invokeApply, ...pass);
});
};
(wrappedInterval as any)['cancel'] = intervalDelegate.cancel;
return wrappedInterval;
}
]);
}
}
])
.run([
$INJECTOR,
($injector: angular.IInjectorService) => {
this.$injector = $injector;
// Initialize the ng1 $injector provider
setTempInjectorRef($injector);
this.injector.get($INJECTOR);
// Put the injector on the DOM, so that it can be "required"
angular.element(element).data !(controllerKey(INJECTOR_KEY), this.injector);
// Wire up the ng1 rootScope to run a digest cycle whenever the zone settles
// We need to do this in the next tick so that we don't prevent the bootup
// stabilizing
setTimeout(() => {
const $rootScope = $injector.get('$rootScope');
const subscription =
this.ngZone.onMicrotaskEmpty.subscribe(() => $rootScope.$digest());
$rootScope.$on('$destroy', () => { subscription.unsubscribe(); });
}, 0);
}
]);
const upgradeModule = angular.module(UPGRADE_MODULE_NAME, [INIT_MODULE_NAME].concat(modules));
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
const windowAngular = (window as any)['angular'];
windowAngular.resumeBootstrap = undefined;
// Bootstrap the AngularJS application inside our zone
this.ngZone.run(() => { angular.bootstrap(element, [upgradeModule.name], config); });
// Patch resumeBootstrap() to run inside the ngZone
if (windowAngular.resumeBootstrap) {
const originalResumeBootstrap: () => void = windowAngular.resumeBootstrap;
const ngZone = this.ngZone;
windowAngular.resumeBootstrap = function() {
let args = arguments;
windowAngular.resumeBootstrap = originalResumeBootstrap;
return ngZone.run(() => windowAngular.resumeBootstrap.apply(this, args));
};
}
}
}