feat(ngUpgrade): add support for upgrade/downgrade of injectables

Closes #4766
This commit is contained in:
Misko Hevery 2015-10-15 12:04:19 -07:00 committed by Miško Hevery
parent 486c1eda8e
commit d896e4350a
2 changed files with 161 additions and 9 deletions

View File

@ -7,10 +7,12 @@ import {
ApplicationRef, ApplicationRef,
AppViewManager, AppViewManager,
Compiler, Compiler,
Inject,
Injector, Injector,
NgZone, NgZone,
PlatformRef, PlatformRef,
ProtoViewRef, ProtoViewRef,
Provider,
Type Type
} from 'angular2/angular2'; } from 'angular2/angular2';
import {applicationDomBindings} from 'angular2/src/core/application_common'; import {applicationDomBindings} from 'angular2/src/core/application_common';
@ -113,6 +115,8 @@ export class UpgradeAdapter {
private upgradedComponents: Type[] = []; private upgradedComponents: Type[] = [];
/* @internal */ /* @internal */
private downgradedComponents: {[name: string]: UpgradeNg1ComponentAdapterBuilder} = {}; private downgradedComponents: {[name: string]: UpgradeNg1ComponentAdapterBuilder} = {};
/* @internal */
private providers: Array<Type | Provider | any[]> = [];
/** /**
* Allows Angular v2 Component to be used from AngularJS v1. * Allows Angular v2 Component to be used from AngularJS v1.
@ -298,7 +302,8 @@ export class UpgradeAdapter {
applicationDomBindings(), applicationDomBindings(),
compilerProviders(), compilerProviders(),
provide(NG1_INJECTOR, {useFactory: () => ng1Injector}), provide(NG1_INJECTOR, {useFactory: () => ng1Injector}),
provide(NG1_COMPILE, {useFactory: () => ng1Injector.get(NG1_COMPILE)}) provide(NG1_COMPILE, {useFactory: () => ng1Injector.get(NG1_COMPILE)}),
this.providers
]); ]);
var injector: Injector = applicationRef.injector; var injector: Injector = applicationRef.injector;
var ngZone: NgZone = injector.get(NgZone); var ngZone: NgZone = injector.get(NgZone);
@ -349,16 +354,125 @@ export class UpgradeAdapter {
Promise.all([this.compileNg2Components(compiler, protoViewRefMap), ng1compilePromise]) Promise.all([this.compileNg2Components(compiler, protoViewRefMap), ng1compilePromise])
.then(() => { .then(() => {
ngZone.run(() => { ngZone.run(() => {
rootScopePrototype.$apply = original$applyFn; // restore original $apply if (rootScopePrototype) {
while (delayApplyExps.length) { rootScopePrototype.$apply = original$applyFn; // restore original $apply
rootScope.$apply(delayApplyExps.shift()); while (delayApplyExps.length) {
rootScope.$apply(delayApplyExps.shift());
}
(<any>upgrade)._bootstrapDone(applicationRef, ng1Injector);
rootScopePrototype = null;
} }
(<any>upgrade)._bootstrapDone(applicationRef, ng1Injector);
}); });
}, onError); }, onError);
return upgrade; return upgrade;
} }
/**
* Adds a provider to the top level environment of a hybrid AngularJS v1 / Angular v2 application.
*
* In hybrid AngularJS v1 / Angular v2 application, there is no one root Angular v2 component,
* for this reason we provide an application global way of registering providers which is
* consistent with single global injection in AngularJS v1.
*
* ## Example
*
* ```
* class Greeter {
* greet(name) {
* alert('Hello ' + name + '!');
* }
* }
*
* @Component({
* selector: 'app',
* template: ''
* })
* class App {
* constructor(greeter: Greeter) {
* this.greeter('World');
* }
* }
*
* var adapter = new UpgradeAdapter();
* adapter.addProvider(Greeter);
*
* var module = angular.module('myExample', []);
* module.directive('app', adapter.downgradeNg2Component(App));
*
* document.body.innerHTML = '<app></app>'
* adapter.bootstrap(document.body, ['myExample']);
*```
*/
public addProvider(provider: Type | Provider | any[]): void { this.providers.push(provider); }
/**
* Allows AngularJS v1 service to be accessible from Angular v2.
*
*
* ## Example
*
* ```
* class Login { ... }
* class Server { ... }
*
* @Injectable()
* class Example {
* constructor(@Inject('server') server, login: Login) {
* ...
* }
* }
*
* var module = angular.module('myExample', []);
* module.service('server', Server);
* module.service('login', Login);
*
* var adapter = new UpgradeAdapter();
* adapter.upgradeNg1Provider('server');
* adapter.upgradeNg1Provider('login', {asToken: Login});
* adapter.addProvider(Example);
*
* adapter.bootstrap(document.body, ['myExample']).ready((ref) => {
* var example: Example = ref.ng2Injector.get(Example);
* });
*
* ```
*/
public upgradeNg1Provider(name: string, options?: {asToken: any}) {
var token = options && options.asToken || name;
this.providers.push(provide(token, {
useFactory: (ng1Injector: angular.auto.IInjectorService) => ng1Injector.get(name),
deps: [NG1_INJECTOR]
}));
}
/**
* Allows Angular v2 service to be accessible from AngularJS v1.
*
*
* ## Example
*
* ```
* class Example {
* }
*
* var adapter = new UpgradeAdapter();
* adapter.addProvider(Example);
*
* var module = angular.module('myExample', []);
* module.factory('example', adapter.downgradeNg2Provider(Example));
*
* adapter.bootstrap(document.body, ['myExample']).ready((ref) => {
* var example: Example = ref.ng1Injector.get('example');
* });
*
* ```
*/
public downgradeNg2Provider(token: any): Function {
var factory = function(injector: Injector) { return injector.get(token); };
factory.$inject = [NG2_INJECTOR];
return factory;
}
/* @internal */ /* @internal */
private compileNg2Components(compiler: Compiler, private compileNg2Components(compiler: Compiler,
protoViewRefMap: ProtoViewRefMap): Promise<ProtoViewRefMap> { protoViewRefMap: ProtoViewRefMap): Promise<ProtoViewRefMap> {
@ -417,14 +531,18 @@ export class UpgradeAdapterRef {
/* @internal */ /* @internal */
private _readyFn: (upgradeAdapterRef?: UpgradeAdapterRef) => void = null; private _readyFn: (upgradeAdapterRef?: UpgradeAdapterRef) => void = null;
public applicationRef: ApplicationRef = null; public ng1RootScope: angular.IRootScopeService = null;
public ng1Injector: angular.auto.IInjectorService = null; public ng1Injector: angular.auto.IInjectorService = null;
public ng2ApplicationRef: ApplicationRef = null;
public ng2Injector: Injector = null;
/* @internal */ /* @internal */
private _bootstrapDone(applicationRef: ApplicationRef, private _bootstrapDone(applicationRef: ApplicationRef,
ng1Injector: angular.auto.IInjectorService) { ng1Injector: angular.auto.IInjectorService) {
this.applicationRef = applicationRef; this.ng2ApplicationRef = applicationRef;
this.ng2Injector = applicationRef.injector;
this.ng1Injector = ng1Injector; this.ng1Injector = ng1Injector;
this.ng1RootScope = ng1Injector.get(NG1_ROOT_SCOPE);
this._readyFn && this._readyFn(this); this._readyFn && this._readyFn(this);
} }
@ -442,6 +560,6 @@ export class UpgradeAdapterRef {
*/ */
public dispose() { public dispose() {
this.ng1Injector.get(NG1_ROOT_SCOPE).$destroy(); this.ng1Injector.get(NG1_ROOT_SCOPE).$destroy();
this.applicationRef.dispose(); this.ng2ApplicationRef.dispose();
} }
} }

View File

@ -11,7 +11,7 @@ import {
xit, xit,
} from 'angular2/testing_internal'; } from 'angular2/testing_internal';
import {Component, Class, Inject, EventEmitter} from 'angular2/angular2'; import {Component, Class, Inject, EventEmitter, ApplicationRef, provide} from 'angular2/angular2';
import {UpgradeAdapter} from 'upgrade/upgrade'; import {UpgradeAdapter} from 'upgrade/upgrade';
export function main() { export function main() {
@ -468,6 +468,40 @@ export function main() {
}); });
describe('injection', () => {
function SomeToken() {}
it('should export ng2 instance to ng1', inject([AsyncTestCompleter], (async) => {
var adapter = new UpgradeAdapter();
var module = angular.module('myExample', []);
adapter.addProvider(provide(SomeToken, {useValue: 'correct_value'}));
module.factory('someToken', adapter.downgradeNg2Provider(SomeToken));
adapter.bootstrap(html('<div>'), ['myExample'])
.ready((ref) => {
expect(ref.ng1Injector.get('someToken')).toBe('correct_value');
ref.dispose();
async.done();
});
}));
it('should export ng1 instance to ng2', inject([AsyncTestCompleter], (async) => {
var adapter = new UpgradeAdapter();
var module = angular.module('myExample', []);
module.value('testValue', 'secreteToken');
adapter.upgradeNg1Provider('testValue');
adapter.upgradeNg1Provider('testValue', {asToken: 'testToken'});
adapter.upgradeNg1Provider('testValue', {asToken: String});
adapter.bootstrap(html('<div>'), ['myExample'])
.ready((ref) => {
expect(ref.ng2Injector.get('testValue')).toBe('secreteToken');
expect(ref.ng2Injector.get(String)).toBe('secreteToken');
expect(ref.ng2Injector.get('testToken')).toBe('secreteToken');
ref.dispose();
async.done();
});
}));
});
describe('examples', () => { describe('examples', () => {
it('should verify UpgradeAdapter example', inject([AsyncTestCompleter], (async) => { it('should verify UpgradeAdapter example', inject([AsyncTestCompleter], (async) => {
var adapter = new UpgradeAdapter(); var adapter = new UpgradeAdapter();