feat(upgrade): support lazy-loading Angular module into AngularJS app
This commit is contained in:

committed by
Alex Rickabaugh

parent
44b50427d9
commit
30e76fcd80
@ -24,6 +24,7 @@ export const $$TESTABILITY = '$$testability';
|
||||
export const COMPILER_KEY = '$$angularCompiler';
|
||||
export const GROUP_PROJECTABLE_NODES_KEY = '$$angularGroupProjectableNodes';
|
||||
export const INJECTOR_KEY = '$$angularInjector';
|
||||
export const LAZY_MODULE_REF = '$$angularLazyModuleRef';
|
||||
export const NG_ZONE_KEY = '$$angularNgZone';
|
||||
|
||||
export const REQUIRE_INJECTOR = '?^^' + INJECTOR_KEY;
|
||||
|
@ -9,9 +9,14 @@
|
||||
import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angular/core';
|
||||
|
||||
import * as angular from './angular1';
|
||||
import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
|
||||
import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, LAZY_MODULE_REF, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
|
||||
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
|
||||
import {controllerKey, getComponentName} from './util';
|
||||
import {LazyModuleRef, controllerKey, getComponentName, isFunction} from './util';
|
||||
|
||||
|
||||
interface Thenable<T> {
|
||||
then(callback: (value: T) => any): any;
|
||||
}
|
||||
|
||||
let downgradeCount = 0;
|
||||
|
||||
@ -50,6 +55,8 @@ let downgradeCount = 0;
|
||||
*/
|
||||
export function downgradeComponent(info: {
|
||||
component: Type<any>;
|
||||
/** @experimental */
|
||||
propagateDigest?: boolean;
|
||||
/** @deprecated since v4. This parameter is no longer used */
|
||||
inputs?: string[];
|
||||
/** @deprecated since v4. This parameter is no longer used */
|
||||
@ -76,9 +83,14 @@ export function downgradeComponent(info: {
|
||||
// triggered by `UpgradeNg1ComponentAdapterBuilder`, before the Angular templates have
|
||||
// been compiled.
|
||||
|
||||
const parentInjector: Injector|ParentInjectorPromise =
|
||||
required[0] || $injector.get(INJECTOR_KEY);
|
||||
const ngModel: angular.INgModelController = required[1];
|
||||
let parentInjector: Injector|Thenable<Injector>|undefined = required[0];
|
||||
let ranAsync = false;
|
||||
|
||||
if (!parentInjector) {
|
||||
const lazyModuleRef = $injector.get(LAZY_MODULE_REF) as LazyModuleRef;
|
||||
parentInjector = lazyModuleRef.injector || lazyModuleRef.promise;
|
||||
}
|
||||
|
||||
const downgradeFn = (injector: Injector) => {
|
||||
const componentFactoryResolver: ComponentFactoryResolver =
|
||||
@ -98,18 +110,26 @@ export function downgradeComponent(info: {
|
||||
|
||||
const projectableNodes = facade.compileContents();
|
||||
facade.createComponent(projectableNodes);
|
||||
facade.setupInputs();
|
||||
facade.setupInputs(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(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
if (parentInjector instanceof ParentInjectorPromise) {
|
||||
if (isThenable<Injector>(parentInjector)) {
|
||||
parentInjector.then(downgradeFn);
|
||||
} else {
|
||||
downgradeFn(parentInjector);
|
||||
}
|
||||
|
||||
ranAsync = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -155,3 +175,7 @@ class ParentInjectorPromise {
|
||||
this.callbacks.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function isThenable<T>(obj: object): obj is Thenable<T> {
|
||||
return isFunction((obj as any).then);
|
||||
}
|
||||
|
@ -18,8 +18,9 @@ const INITIAL_VALUE = {
|
||||
};
|
||||
|
||||
export class DowngradeComponentAdapter {
|
||||
private implementsOnChanges = false;
|
||||
private inputChangeCount: number = 0;
|
||||
private inputChanges: SimpleChanges|null = null;
|
||||
private inputChanges: SimpleChanges = {};
|
||||
private componentScope: angular.IScope;
|
||||
private componentRef: ComponentRef<any>|null = null;
|
||||
private component: any = null;
|
||||
@ -64,7 +65,7 @@ export class DowngradeComponentAdapter {
|
||||
hookupNgModel(this.ngModel, this.component);
|
||||
}
|
||||
|
||||
setupInputs(): void {
|
||||
setupInputs(propagateDigest = true): void {
|
||||
const attrs = this.attrs;
|
||||
const inputs = this.componentFactory.inputs || [];
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
@ -114,17 +115,29 @@ export class DowngradeComponentAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke `ngOnChanges()` and Change Detection (when necessary)
|
||||
const detectChanges = () => this.changeDetector && this.changeDetector.detectChanges();
|
||||
const prototype = this.componentFactory.componentType.prototype;
|
||||
if (prototype && (<OnChanges>prototype).ngOnChanges) {
|
||||
// Detect: OnChanges interface
|
||||
this.inputChanges = {};
|
||||
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
||||
this.implementsOnChanges = !!(prototype && (<OnChanges>prototype).ngOnChanges);
|
||||
|
||||
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
||||
// Invoke `ngOnChanges()`
|
||||
if (this.implementsOnChanges) {
|
||||
const inputChanges = this.inputChanges;
|
||||
this.inputChanges = {};
|
||||
(<OnChanges>this.component).ngOnChanges(inputChanges !);
|
||||
});
|
||||
}
|
||||
|
||||
// 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(detectChanges);
|
||||
}
|
||||
this.componentScope.$watch(() => this.changeDetector && this.changeDetector.detectChanges());
|
||||
}
|
||||
|
||||
setupOutputs() {
|
||||
@ -181,11 +194,11 @@ export class DowngradeComponentAdapter {
|
||||
getInjector(): Injector { return this.componentRef ! && this.componentRef !.injector; }
|
||||
|
||||
private updateInput(prop: string, prevValue: any, currValue: any) {
|
||||
if (this.inputChanges) {
|
||||
this.inputChangeCount++;
|
||||
if (this.implementsOnChanges) {
|
||||
this.inputChanges[prop] = new SimpleChange(prevValue, currValue, prevValue === currValue);
|
||||
}
|
||||
|
||||
this.inputChangeCount++;
|
||||
this.component[prop] = currValue;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {Injector, Type} from '@angular/core';
|
||||
import * as angular from './angular1';
|
||||
|
||||
const DIRECTIVE_PREFIX_REGEXP = /^(?:x|data)[:\-_]/i;
|
||||
@ -67,6 +67,11 @@ export class Deferred<R> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface LazyModuleRef {
|
||||
injector?: Injector;
|
||||
promise: Promise<Injector>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the passed-in component implements the subset of the
|
||||
* `ControlValueAccessor` interface needed for AngularJS `ng-model`
|
||||
|
Reference in New Issue
Block a user