diff --git a/aio/content/guide/upgrade-performance.md b/aio/content/guide/upgrade-performance.md index 3ec8f1e4cb..f8089db313 100644 --- a/aio/content/guide/upgrade-performance.md +++ b/aio/content/guide/upgrade-performance.md @@ -281,22 +281,28 @@ The differences between `downgradeModule()` and `UpgradeModule` end here. The re `upgrade/static` APIs and concepts work in the exact same way for both types of hybrid apps. See [Upgrading from AngularJS](guide/upgrade) to learn about: -- [Using Angular Components from AngularJS Code](guide/upgrade#using-angular-components-from-angularjs-code). +- [Using Angular Components from AngularJS Code](guide/upgrade#using-angular-components-from-angularjs-code).
+ _NOTE: If you are downgrading multiple modules, you need to specify the name of the downgraded + module each component belongs to, when calling `downgradeComponent()`._ - [Using AngularJS Component Directives from Angular Code](guide/upgrade#using-angularjs-component-directives-from-angular-code). - [Projecting AngularJS Content into Angular Components](guide/upgrade#projecting-angularjs-content-into-angular-components). - [Transcluding Angular Content into AngularJS Component Directives](guide/upgrade#transcluding-angular-content-into-angularjs-component-directives). - [Making AngularJS Dependencies Injectable to Angular](guide/upgrade#making-angularjs-dependencies-injectable-to-angular). -- [Making Angular Dependencies Injectable to AngularJS](guide/upgrade#making-angular-dependencies-injectable-to-angularjs). +- [Making Angular Dependencies Injectable to AngularJS](guide/upgrade#making-angular-dependencies-injectable-to-angularjs).
+ _NOTE: If you are downgrading multiple modules, you need to specify the name of the downgraded + module each injectable belongs to, when calling `downgradeInjectable()`._
While it is possible to downgrade injectables, downgraded injectables will not be available until - the Angular module 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 that is controlled by Angular. + 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 Angular components, but it is _not OK_ to use it in an AngularJS component that may be used - independently of Angular. + 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.
diff --git a/packages/examples/upgrade/static/ts/lite-multi/e2e_test/static_lite_multi_spec.ts b/packages/examples/upgrade/static/ts/lite-multi/e2e_test/static_lite_multi_spec.ts new file mode 100644 index 0000000000..3a4d571243 --- /dev/null +++ b/packages/examples/upgrade/static/ts/lite-multi/e2e_test/static_lite_multi_spec.ts @@ -0,0 +1,28 @@ +/** + * @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 {browser, by, element} from 'protractor'; + +import {verifyNoBrowserErrors} from '../../../../../_common/e2e_util'; + + +describe('upgrade/static (lite with multiple downgraded modules)', () => { + const navButtons = element.all(by.css('nav button')); + const mainContent = element(by.css('main')); + + beforeEach(() => browser.get('/upgrade/static/ts/lite-multi/')); + afterEach(verifyNoBrowserErrors); + + it('should correctly bootstrap multiple downgraded modules', () => { + navButtons.get(1).click(); + expect(mainContent.getText()).toBe('Component B'); + + navButtons.get(0).click(); + expect(mainContent.getText()).toBe('Component A | ng1(ng2)'); + }); +}); diff --git a/packages/examples/upgrade/static/ts/lite-multi/module.ts b/packages/examples/upgrade/static/ts/lite-multi/module.ts new file mode 100644 index 0000000000..d53a066e6d --- /dev/null +++ b/packages/examples/upgrade/static/ts/lite-multi/module.ts @@ -0,0 +1,118 @@ +/** + * @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 + */ + +// #docplaster +import {Component, Directive, ElementRef, Injectable, Injector, NgModule, StaticProvider, getPlatform} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; +import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {UpgradeComponent, downgradeComponent, downgradeInjectable, downgradeModule} from '@angular/upgrade/static'; + + +declare var angular: ng.IAngularStatic; + +// An Angular module that declares an Angular service and a component, +// which in turn uses an upgraded AngularJS component. +@Component({ + selector: 'ng2A', + template: 'Component A | ', +}) +export class Ng2AComponent { +} + +@Directive({ + selector: 'ng1A', +}) +export class Ng1AComponentFacade extends UpgradeComponent { + constructor(elementRef: ElementRef, injector: Injector) { super('ng1A', elementRef, injector); } +} + +@Injectable() +export class Ng2AService { + getValue() { return 'ng2'; } +} + +@NgModule({ + imports: [BrowserModule], + providers: [Ng2AService], + declarations: [Ng1AComponentFacade, Ng2AComponent], + entryComponents: [Ng2AComponent], +}) +export class Ng2AModule { + ngDoBootstrap() {} +} + + +// Another Angular module that declares an Angular component. +@Component({ + selector: 'ng2B', + template: 'Component B', +}) +export class Ng2BComponent { +} + +@NgModule({ + imports: [BrowserModule], + declarations: [Ng2BComponent], + entryComponents: [Ng2BComponent], +}) +export class Ng2BModule { + ngDoBootstrap() {} +} + + +// The downgraded Angular modules. +const downgradedNg2AModule = downgradeModule( + (extraProviders: StaticProvider[]) => + (getPlatform() || platformBrowserDynamic(extraProviders)).bootstrapModule(Ng2AModule)); + +const downgradedNg2BModule = downgradeModule( + (extraProviders: StaticProvider[]) => + (getPlatform() || platformBrowserDynamic(extraProviders)).bootstrapModule(Ng2BModule)); + + +// The AngularJS app including downgraded modules, components and injectables. +const appModule = + angular.module('exampleAppModule', [downgradedNg2AModule, downgradedNg2BModule]) + .component('exampleApp', { + template: ` + +
+
+ + +
+ `, + controller: class ExampleAppController{page = 'A';}, + }) + .component('ng1A', { + template: 'ng1({{ $ctrl.value }})', + controller: [ + 'ng2AService', class Ng1AController{ + value = this.ng2AService.getValue(); constructor(private ng2AService: Ng2AService) {} + } + ], + }) + .directive('ng2A', downgradeComponent({ + component: Ng2AComponent, + downgradedModule: downgradedNg2AModule, + propagateDigest: false, + })) + .directive('ng2B', downgradeComponent({ + component: Ng2BComponent, + downgradedModule: downgradedNg2BModule, + propagateDigest: false, + })) + .factory('ng2AService', downgradeInjectable(Ng2AService, downgradedNg2AModule)); + + +// Bootstrap the AngularJS app. +angular.bootstrap(document.body, [appModule.name]); diff --git a/packages/examples/upgrade/static/ts/lite/module.ts b/packages/examples/upgrade/static/ts/lite/module.ts index 0a2e9b95f5..dec1613ebb 100644 --- a/packages/examples/upgrade/static/ts/lite/module.ts +++ b/packages/examples/upgrade/static/ts/lite/module.ts @@ -17,7 +17,6 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; /* tslint:disable: no-duplicate-imports */ import {UpgradeComponent} from '@angular/upgrade/static'; import {downgradeComponent} from '@angular/upgrade/static'; -import {downgradeInjectable} from '@angular/upgrade/static'; // #docregion basic-how-to import {downgradeModule} from '@angular/upgrade/static'; // #enddocregion diff --git a/packages/upgrade/src/common/downgrade_component.ts b/packages/upgrade/src/common/downgrade_component.ts index 2fb420de7d..aebf63a81f 100644 --- a/packages/upgrade/src/common/downgrade_component.ts +++ b/packages/upgrade/src/common/downgrade_component.ts @@ -46,8 +46,14 @@ interface Thenable { * * @param info contains information about the Component that is being downgraded: * - * * `component: Type`: The type of the Component that will be downgraded - * * `propagateDigest?: boolean`: Whether to perform {@link ChangeDetectorRef#detectChanges + * - `component: Type`: 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. + *
+ * (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. @@ -59,7 +65,7 @@ interface Thenable { * @publicApi */ export function downgradeComponent(info: { - component: Type; propagateDigest?: boolean; + component: Type; downgradedModule?: string; propagateDigest?: boolean; /** @deprecated since v4. This parameter is no longer used */ inputs?: string[]; /** @deprecated since v4. This parameter is no longer used */ @@ -96,7 +102,9 @@ export function downgradeComponent(info: { let ranAsync = false; if (!parentInjector) { - const lazyModuleRef = $injector.get(LAZY_MODULE_REF) as LazyModuleRef; + const downgradedModule = info.downgradedModule || ''; + const lazyModuleRefKey = `${LAZY_MODULE_REF}${downgradedModule}`; + const lazyModuleRef = $injector.get(lazyModuleRefKey) as LazyModuleRef; needsNgZone = lazyModuleRef.needsNgZone; parentInjector = lazyModuleRef.injector || lazyModuleRef.promise as Promise; } diff --git a/packages/upgrade/src/common/downgrade_injectable.ts b/packages/upgrade/src/common/downgrade_injectable.ts index d0cc47d014..443070cc66 100644 --- a/packages/upgrade/src/common/downgrade_injectable.ts +++ b/packages/upgrade/src/common/downgrade_injectable.ts @@ -43,16 +43,35 @@ import {INJECTOR_KEY} from './constants'; * * {@example upgrade/static/ts/full/module.ts region="example-app"} * + *
+ * + * 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. + * + *
+ * * @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.
+ * (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 */ -export function downgradeInjectable(token: any): Function { +export function downgradeInjectable(token: any, downgradedModule: string = ''): Function { const factory = function(i: Injector) { return i.get(token); }; - (factory as any)['$inject'] = [INJECTOR_KEY]; + (factory as any)['$inject'] = [`${INJECTOR_KEY}${downgradedModule}`]; return factory; } diff --git a/packages/upgrade/src/static/downgrade_module.ts b/packages/upgrade/src/static/downgrade_module.ts index 00129fda76..3fa96d3f55 100644 --- a/packages/upgrade/src/static/downgrade_module.ts +++ b/packages/upgrade/src/static/downgrade_module.ts @@ -17,6 +17,8 @@ import {angular1Providers, setTempInjectorRef} from './angular1_providers'; import {NgAdapterInjector} from './util'; +let moduleUid = 0; + /** * @description * @@ -104,7 +106,10 @@ import {NgAdapterInjector} from './util'; export function downgradeModule( moduleFactoryOrBootstrapFn: NgModuleFactory| ((extraProviders: StaticProvider[]) => Promise>)): string { - const LAZY_MODULE_NAME = UPGRADE_MODULE_NAME + '.lazy'; + const lazyModuleName = `${UPGRADE_MODULE_NAME}.lazy${++moduleUid}`; + const lazyModuleRefKey = `${LAZY_MODULE_REF}${lazyModuleName}`; + const lazyInjectorKey = `${INJECTOR_KEY}${lazyModuleName}`; + const bootstrapFn = isFunction(moduleFactoryOrBootstrapFn) ? moduleFactoryOrBootstrapFn : (extraProviders: StaticProvider[]) => @@ -113,9 +118,10 @@ export function downgradeModule( let injector: Injector; // Create an ng1 module to bootstrap. - angular.module(LAZY_MODULE_NAME, []) + angular.module(lazyModuleName, []) + .factory(INJECTOR_KEY, [lazyInjectorKey, identity]) .factory( - INJECTOR_KEY, + lazyInjectorKey, () => { if (!injector) { throw new Error( @@ -123,7 +129,8 @@ export function downgradeModule( } return injector; }) - .factory(LAZY_MODULE_REF, [ + .factory(LAZY_MODULE_REF, [lazyModuleRefKey, identity]) + .factory(lazyModuleRefKey, [ $INJECTOR, ($injector: angular.IInjectorService) => { setTempInjectorRef($injector); @@ -140,5 +147,9 @@ export function downgradeModule( } ]); - return LAZY_MODULE_NAME; + return lazyModuleName; +} + +function identity(x: T): T { + return x; } diff --git a/packages/upgrade/test/common/downgrade_injectable_spec.ts b/packages/upgrade/test/common/downgrade_injectable_spec.ts index 628d8f1237..ce6167ba2d 100644 --- a/packages/upgrade/test/common/downgrade_injectable_spec.ts +++ b/packages/upgrade/test/common/downgrade_injectable_spec.ts @@ -21,5 +21,16 @@ import {downgradeInjectable} from '@angular/upgrade/src/common/downgrade_injecta expect(injector.get).toHaveBeenCalledWith('someToken'); expect(value).toEqual('service value'); }); + + it('should inject the specified module\'s injector when specifying a module name', () => { + const factory = downgradeInjectable('someToken', 'someModule'); + expect(factory).toEqual(jasmine.any(Function)); + expect((factory as any).$inject).toEqual([`${INJECTOR_KEY}someModule`]); + + const injector = {get: jasmine.createSpy('get').and.returnValue('service value')}; + const value = factory(injector); + expect(injector.get).toHaveBeenCalledWith('someToken'); + expect(value).toEqual('service value'); + }); }); } diff --git a/packages/upgrade/test/static/integration/downgrade_module_spec.ts b/packages/upgrade/test/static/integration/downgrade_module_spec.ts index d751d9a06b..c142e2678a 100644 --- a/packages/upgrade/test/static/integration/downgrade_module_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_module_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ApplicationRef, Component, DoCheck, Inject, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, OnInit, StaticProvider, ViewRef, destroyPlatform} from '@angular/core'; +import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ApplicationRef, Component, DoCheck, Inject, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, OnInit, StaticProvider, Type, ViewRef, destroyPlatform, getPlatform} from '@angular/core'; import {async, fakeAsync, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; @@ -25,6 +25,58 @@ withEachNg1Version(() => { beforeEach(() => destroyPlatform()); + it('should support multiple downgraded modules', async(() => { + @Component({selector: 'ng2A', template: 'a'}) + class Ng2ComponentA { + } + + @Component({selector: 'ng2B', template: 'b'}) + class Ng2ComponentB { + } + + @NgModule({ + declarations: [Ng2ComponentA], + entryComponents: [Ng2ComponentA], + imports: [BrowserModule], + }) + class Ng2ModuleA { + ngDoBootstrap() {} + } + + @NgModule({ + declarations: [Ng2ComponentB], + entryComponents: [Ng2ComponentB], + imports: [BrowserModule], + }) + class Ng2ModuleB { + ngDoBootstrap() {} + } + + const doDowngradeModule = (module: Type) => { + const bootstrapFn = (extraProviders: StaticProvider[]) => + (getPlatform() || platformBrowserDynamic(extraProviders)).bootstrapModule(module); + return downgradeModule(bootstrapFn); + }; + + const downModA = doDowngradeModule(Ng2ModuleA); + const downModB = doDowngradeModule(Ng2ModuleB); + const ng1Module = angular.module('ng1', [downModA, downModB]) + .directive('ng2A', downgradeComponent({ + component: Ng2ComponentA, + downgradedModule: downModA, propagateDigest, + })) + .directive('ng2B', downgradeComponent({ + component: Ng2ComponentB, + downgradedModule: downModB, propagateDigest, + })); + + const element = html(' | '); + angular.bootstrap(element, [ng1Module.name]); + + // Wait for the module to be bootstrapped. + setTimeout(() => expect(element.textContent).toBe('a | b')); + })); + it('should support downgrading a component and propagate inputs', async(() => { @Component( {selector: 'ng2A', template: 'a({{ value }}) | '}) diff --git a/tools/public_api_guard/upgrade/static.d.ts b/tools/public_api_guard/upgrade/static.d.ts index d36be96388..47b7425ce9 100644 --- a/tools/public_api_guard/upgrade/static.d.ts +++ b/tools/public_api_guard/upgrade/static.d.ts @@ -1,12 +1,13 @@ export declare function downgradeComponent(info: { component: Type; + downgradedModule?: string; propagateDigest?: boolean; /** @deprecated */ inputs?: string[]; /** @deprecated */ outputs?: string[]; /** @deprecated */ selectors?: string[]; }): any; -export declare function downgradeInjectable(token: any): Function; +export declare function downgradeInjectable(token: any, downgradedModule?: string): Function; export declare function downgradeModule(moduleFactoryOrBootstrapFn: NgModuleFactory | ((extraProviders: StaticProvider[]) => Promise>)): string;