refactor(upgrade/upgrade_adapter): use `Deferred` helper

Making Angular 1's `$compile` asynchronous by chaining injector promises
in linking functions can cause flickering views in applications.
This commit is contained in:
Peter Bacon Darwin 2016-11-03 14:48:11 +00:00 committed by Victor Berchet
parent eab7e490c9
commit d6e5e9283c
2 changed files with 59 additions and 48 deletions

View File

@ -14,7 +14,7 @@ import {NG1_COMPILE, NG1_INJECTOR, NG1_PARSE, NG1_ROOT_SCOPE, NG1_TESTABILITY, N
import {DowngradeNg2ComponentAdapter} from './downgrade_ng2_adapter'; import {DowngradeNg2ComponentAdapter} from './downgrade_ng2_adapter';
import {ComponentInfo, getComponentInfo} from './metadata'; import {ComponentInfo, getComponentInfo} from './metadata';
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter'; import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
import {controllerKey, onError} from './util'; import {controllerKey, onError, Deferred} from './util';
let upgradeCount: number = 0; let upgradeCount: number = 0;
@ -326,7 +326,7 @@ export class UpgradeAdapter {
const componentFactoryRefMap: ComponentFactoryRefMap = {}; const componentFactoryRefMap: ComponentFactoryRefMap = {};
const ng1Module = angular.module(this.idPrefix, modules); const ng1Module = angular.module(this.idPrefix, modules);
let ng1BootstrapPromise: Promise<any>; let ng1BootstrapPromise: Promise<any>;
let ng1compilePromise: Promise<any>; const ng2BootstrapDeferred = new Deferred();
ng1Module.factory(NG2_INJECTOR, () => moduleRef.injector.get(Injector)) ng1Module.factory(NG2_INJECTOR, () => moduleRef.injector.get(Injector))
.value(NG2_ZONE, ngZone) .value(NG2_ZONE, ngZone)
.factory(NG2_COMPILER, () => moduleRef.injector.get(Compiler)) .factory(NG2_COMPILER, () => moduleRef.injector.get(Compiler))
@ -375,54 +375,52 @@ export class UpgradeAdapter {
} }
]); ]);
ng1compilePromise = new Promise((resolve, reject) => { ng1Module.run([
ng1Module.run([ '$injector', '$rootScope',
'$injector', '$rootScope', (injector: angular.IInjectorService, rootScope: angular.IRootScopeService) => {
(injector: angular.IInjectorService, rootScope: angular.IRootScopeService) => { ng1Injector = injector;
ng1Injector = injector; UpgradeNg1ComponentAdapterBuilder.resolve(this.ng1ComponentsToBeUpgraded, injector)
UpgradeNg1ComponentAdapterBuilder.resolve(this.ng1ComponentsToBeUpgraded, injector) .then(() => {
.then(() => { // At this point we have ng1 injector and we have lifted ng1 components into ng2, we
// At this point we have ng1 injector and we have lifted ng1 components into ng2, we // now can bootstrap ng2.
// now can bootstrap ng2. const DynamicNgUpgradeModule =
const DynamicNgUpgradeModule = NgModule({
NgModule({ providers: [
providers: [ {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
this.providers ],
], imports: [this.ng2AppModule]
imports: [this.ng2AppModule] }).Class({
}).Class({ constructor: function DynamicNgUpgradeModule() {},
constructor: function DynamicNgUpgradeModule() {}, ngDoBootstrap: function() {}
ngDoBootstrap: function() {} });
});
(platformBrowserDynamic() as any) (platformBrowserDynamic() as any)
._bootstrapModuleWithZone( ._bootstrapModuleWithZone(
DynamicNgUpgradeModule, this.compilerOptions, ngZone, DynamicNgUpgradeModule, this.compilerOptions, ngZone,
(componentFactories: ComponentFactory<any>[]) => { (componentFactories: ComponentFactory<any>[]) => {
componentFactories.forEach((componentFactory: ComponentFactory<any>) => { componentFactories.forEach((componentFactory: ComponentFactory<any>) => {
const type: Type<any> = componentFactory.componentType; const type: Type<any> = componentFactory.componentType;
if (this.upgradedComponents.indexOf(type) !== -1) { if (this.upgradedComponents.indexOf(type) !== -1) {
componentFactoryRefMap[getComponentInfo(type).selector] = componentFactoryRefMap[getComponentInfo(type).selector] =
componentFactory; componentFactory;
} }
}); });
}) })
.then((ref: NgModuleRef<any>) => { .then((ref: NgModuleRef<any>) => {
moduleRef = ref; moduleRef = ref;
angular.element(element).data( angular.element(element).data(
controllerKey(NG2_INJECTOR), moduleRef.injector); controllerKey(NG2_INJECTOR), moduleRef.injector);
ngZone.onMicrotaskEmpty.subscribe({ ngZone.onMicrotaskEmpty.subscribe({
next: (_: any) => ngZone.runOutsideAngular(() => rootScope.$evalAsync()) next: (_: any) => ngZone.runOutsideAngular(() => rootScope.$evalAsync())
}); });
}) })
.then(resolve, reject); .then(ng2BootstrapDeferred.resolve, ng2BootstrapDeferred.reject);
}) })
.catch(reject); .catch(ng2BootstrapDeferred.reject);
} }
]); ]);
});
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred // Make sure resumeBootstrap() only exists if the current bootstrap is deferred
const windowAngular = (window as any /** TODO #???? */)['angular']; const windowAngular = (window as any /** TODO #???? */)['angular'];
@ -443,7 +441,7 @@ export class UpgradeAdapter {
} }
}); });
Promise.all([ng1BootstrapPromise, ng1compilePromise]).then(() => { Promise.all([ng1BootstrapPromise, ng2BootstrapDeferred.promise]).then(() => {
moduleRef.injector.get(NgZone).run(() => { moduleRef.injector.get(NgZone).run(() => {
if (rootScopePrototype) { if (rootScopePrototype) {
rootScopePrototype.$apply = original$applyFn; // restore original $apply rootScopePrototype.$apply = original$applyFn; // restore original $apply

View File

@ -22,3 +22,16 @@ export function onError(e: any) {
export function controllerKey(name: string): string { export function controllerKey(name: string): string {
return '$' + name + 'Controller'; return '$' + name + 'Controller';
} }
export class Deferred<R> {
promise: Promise<R>;
resolve: (value?: R|PromiseLike<R>) => void;
reject: (error?: any) => void;
constructor() {
this.promise = new Promise((res, rej) => {
this.resolve = res;
this.reject = rej;
});
}
}