/**
* @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 {CssSelector, SelectorMatcher, createElementCssSelector} from '@angular/compiler';
import {Compiler, CompilerOptions, ComponentFactory, Injector, NgModule, NgModuleRef, NgZone, Provider, Testability, Type} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from './angular_js';
import {NG1_COMPILE, NG1_INJECTOR, NG1_PARSE, NG1_ROOT_SCOPE, NG1_TESTABILITY, NG2_COMPILER, NG2_COMPONENT_FACTORY_REF_MAP, NG2_INJECTOR, NG2_ZONE, REQUIRE_INJECTOR} from './constants';
import {DowngradeNg2ComponentAdapter} from './downgrade_ng2_adapter';
import {ComponentInfo, getComponentInfo} from './metadata';
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
import {Deferred, controllerKey, getAttributesAsArray, onError} from './util';
let upgradeCount: number = 0;
/**
* Use `UpgradeAdapter` to allow Angular 1 and Angular 2+ to coexist in a single application.
*
* The `UpgradeAdapter` allows:
* 1. creation of Angular 2+ component from Angular 1 component directive
* (See [UpgradeAdapter#upgradeNg1Component()])
* 2. creation of Angular 1 directive from Angular 2+ component.
* (See [UpgradeAdapter#downgradeNg2Component()])
* 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks
* coexisting in a single application.
*
* ## 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. Angular 1 directives always execute inside Angular 1 framework codebase regardless of
* where they are instantiated.
* 4. Angular 2+ components always execute inside Angular 2+ framework codebase regardless of
* where they are instantiated.
* 5. An Angular 1 component can be upgraded to an Angular 2+ component. This creates an
* Angular 2+ directive, which bootstraps the Angular 1 component directive in that location.
* 6. An Angular 2+ component can be downgraded to an Angular 1 component directive. This creates
* an Angular 1 directive, which bootstraps the Angular 2+ component in that location.
* 7. Whenever an adapter 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. This implies that component bindings will always follow the semantics of the
* instantiation framework. The syntax is always that of Angular 2+ syntax.
* 8. Angular 1 is always bootstrapped first and owns the bottom most view.
* 9. The new application is running in Angular 2+ zone, and therefore it no longer needs calls to
* `$apply()`.
*
* ### Example
*
* ```
* const adapter = new UpgradeAdapter(forwardRef(() => MyNg2Module), myCompilerOptions);
* const module = angular.module('myExample', []);
* module.directive('ng2Comp', adapter.downgradeNg2Component(Ng2Component));
*
* module.directive('ng1Hello', function() {
* return {
* scope: { title: '=' },
* template: 'ng1[Hello {{title}}!]()'
* };
* });
*
*
* @Component({
* selector: 'ng2-comp',
* inputs: ['name'],
* template: 'ng2[transclude]()',
* directives:
* })
* class Ng2Component {
* }
*
* @NgModule({
* declarations: [Ng2Component, adapter.upgradeNg1Component('ng1Hello')],
* imports: [BrowserModule]
* })
* class MyNg2Module {}
*
*
* document.body.innerHTML = 'project';
*
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
* expect(document.body.textContent).toEqual(
* "ng2[ng1[Hello World!](transclude)](project)");
* });
*
* ```
*
* @stable
*/
export class UpgradeAdapter {
private idPrefix: string = `NG2_UPGRADE_${upgradeCount++}_`;
private upgradedComponents: Type[] = [];
/**
* An internal map of ng1 components which need to up upgraded to ng2.
*
* We can't upgrade until injector is instantiated and we can retrieve the component metadata.
* For this reason we keep a list of components to upgrade until ng1 injector is bootstrapped.
*
* @internal
*/
private ng1ComponentsToBeUpgraded: {[name: string]: UpgradeNg1ComponentAdapterBuilder} = {};
private providers: Provider[] = [];
private ngZone: NgZone;
private ng1Module: angular.IModule;
private moduleRef: NgModuleRef = null;
private ng2BootstrapDeferred: Deferred;
constructor(private ng2AppModule: Type, private compilerOptions?: CompilerOptions) {
if (!ng2AppModule) {
throw new Error(
'UpgradeAdapter cannot be instantiated without an NgModule of the Angular 2 app.');
}
}
/**
* Allows Angular 2+ Component to be used from Angular 1.
*
* Use `downgradeNg2Component` to create an Angular 1 Directive Definition Factory from
* Angular 2+ Component. The adapter will bootstrap Angular 2+ component from within the
* Angular 1 template.
*
* ## Mental Model
*
* 1. The component is instantiated by being listed in Angular 1 template. This means that the
* host element is controlled by Angular 1, but the component's view will be controlled by
* Angular 2+.
* 2. Even thought the component is instantiated in Angular 1, it will be using Angular 2+
* syntax. This has to be done, this way because we must follow Angular 2+ components do not
* declare how the attributes should be interpreted.
*
* ## Supported Features
*
* - Bindings:
* - Attribute: ``
* - Interpolation: ``
* - Expression: ``
* - Event: ``
* - Content projection: yes
*
* ### Example
*
* ```
* const adapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
* const module = angular.module('myExample', []);
* module.directive('greet', adapter.downgradeNg2Component(Greeter));
*
* @Component({
* selector: 'greet',
* template: '{{salutation}} {{name}}! - '
* })
* class Greeter {
* @Input() salutation: string;
* @Input() name: string;
* }
*
* @NgModule({
* declarations: [Greeter],
* imports: [BrowserModule]
* })
* class MyNg2Module {}
*
* document.body.innerHTML =
* 'ng1 template: text';
*
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
* expect(document.body.textContent).toEqual("ng1 template: Hello world! - text");
* });
* ```
*/
downgradeNg2Component(type: Type): Function {
this.upgradedComponents.push(type);
const info: ComponentInfo = getComponentInfo(type);
return ng1ComponentDirective(info, `${this.idPrefix}${info.selector}_c`);
}
/**
* Allows Angular 1 Component to be used from Angular 2+.
*
* Use `upgradeNg1Component` to create an Angular 2+ component from Angular 1 Component
* directive. The adapter will bootstrap Angular 1 component from within the Angular 2+
* template.
*
* ## Mental Model
*
* 1. The component is instantiated by being listed in Angular 2+ template. This means that the
* host element is controlled by Angular 2+, but the component's view will be controlled by
* Angular 1.
*
* ## Supported Features
*
* - Bindings:
* - Attribute: ``
* - Interpolation: ``
* - Expression: ``
* - Event: ``
* - Transclusion: yes
* - Only some of the features of
* [Directive Definition Object](https://docs.angularjs.org/api/ng/service/$compile) are
* supported:
* - `compile`: not supported because the host element is owned by Angular 2+, which does
* not allow modifying DOM structure during compilation.
* - `controller`: supported. (NOTE: injection of `$attrs` and `$transclude` is not supported.)
* - `controllerAs`: supported.
* - `bindToController`: supported.
* - `link`: supported. (NOTE: only pre-link function is supported.)
* - `name`: supported.
* - `priority`: ignored.
* - `replace`: not supported.
* - `require`: supported.
* - `restrict`: must be set to 'E'.
* - `scope`: supported.
* - `template`: supported.
* - `templateUrl`: supported.
* - `terminal`: ignored.
* - `transclude`: supported.
*
*
* ### Example
*
* ```
* const adapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
* const module = angular.module('myExample', []);
*
* module.directive('greet', function() {
* return {
* scope: {salutation: '=', name: '=' },
* template: '{{salutation}} {{name}}! - '
* };
* });
*
* module.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
*
* @Component({
* selector: 'ng2',
* template: 'ng2 template: text'
* })
* class Ng2Component {
* }
*
* @NgModule({
* declarations: [Ng2Component, adapter.upgradeNg1Component('greet')],
* imports: [BrowserModule]
* })
* class MyNg2Module {}
*
* document.body.innerHTML = '';
*
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
* expect(document.body.textContent).toEqual("ng2 template: Hello world! - text");
* });
* ```
*/
upgradeNg1Component(name: string): Type {
if ((this.ng1ComponentsToBeUpgraded).hasOwnProperty(name)) {
return this.ng1ComponentsToBeUpgraded[name].type;
} else {
return (this.ng1ComponentsToBeUpgraded[name] = new UpgradeNg1ComponentAdapterBuilder(name))
.type;
}
}
/**
* Registers the adapter's Angular 1 upgrade module for unit testing in Angular 1.
* Use this instead of `angular.mock.module()` to load the upgrade module into
* the Angular 1 testing injector.
*
* ### Example
*
* ```
* const upgradeAdapter = new UpgradeAdapter(MyNg2Module);
*
* // configure the adapter with upgrade/downgrade components and services
* upgradeAdapter.downgradeNg2Component(MyComponent);
*
* let upgradeAdapterRef: UpgradeAdapterRef;
* let $compile, $rootScope;
*
* // We must register the adapter before any calls to `inject()`
* beforeEach(() => {
* upgradeAdapterRef = upgradeAdapter.registerForNg1Tests(['heroApp']);
* });
*
* beforeEach(inject((_$compile_, _$rootScope_) => {
* $compile = _$compile_;
* $rootScope = _$rootScope_;
* }));
*
* it("says hello", (done) => {
* upgradeAdapterRef.ready(() => {
* const element = $compile("")($rootScope);
* $rootScope.$apply();
* expect(element.html()).toContain("Hello World");
* done();
* })
* });
*
* ```
*
* @param modules any Angular 1 modules that the upgrade module should depend upon
* @returns an {@link UpgradeAdapterRef}, which lets you register a `ready()` callback to
* run assertions once the Angular 2+ components are ready to test through Angular 1.
*/
registerForNg1Tests(modules?: string[]): UpgradeAdapterRef {
const windowNgMock = (window as any)['angular'].mock;
if (!windowNgMock || !windowNgMock.module) {
throw new Error('Failed to find \'angular.mock.module\'.');
}
this.declareNg1Module(modules);
windowNgMock.module(this.ng1Module.name);
const upgrade = new UpgradeAdapterRef();
this.ng2BootstrapDeferred.promise.then(
(ng1Injector) => { (upgrade)._bootstrapDone(this.moduleRef, ng1Injector); }, onError);
return upgrade;
}
/**
* Bootstrap a hybrid Angular 1 / Angular 2+ application.
*
* This `bootstrap` method is a direct replacement (takes same arguments) for Angular 1
* [`bootstrap`](https://docs.angularjs.org/api/ng/function/angular.bootstrap) method. Unlike
* Angular 1, this bootstrap is asynchronous.
*
* ### Example
*
* ```
* const adapter = new UpgradeAdapter(MyNg2Module);
* const module = angular.module('myExample', []);
* module.directive('ng2', adapter.downgradeNg2Component(Ng2));
*
* module.directive('ng1', function() {
* return {
* scope: { title: '=' },
* template: 'ng1[Hello {{title}}!]()'
* };
* });
*
*
* @Component({
* selector: 'ng2',
* inputs: ['name'],
* template: 'ng2[transclude]()'
* })
* class Ng2 {
* }
*
* @NgModule({
* declarations: [Ng2, adapter.upgradeNg1Component('ng1')],
* imports: [BrowserModule]
* })
* class MyNg2Module {}
*
* document.body.innerHTML = 'project';
*
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
* expect(document.body.textContent).toEqual(
* "ng2[ng1[Hello World!](transclude)](project)");
* });
* ```
*/
bootstrap(element: Element, modules?: any[], config?: angular.IAngularBootstrapConfig):
UpgradeAdapterRef {
this.declareNg1Module(modules);
const upgrade = new UpgradeAdapterRef();
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
const windowAngular = (window as any /** TODO #???? */)['angular'];
windowAngular.resumeBootstrap = undefined;
this.ngZone.run(() => { angular.bootstrap(element, [this.ng1Module.name], config); });
const ng1BootstrapPromise = new Promise((resolve) => {
if (windowAngular.resumeBootstrap) {
const originalResumeBootstrap: () => void = windowAngular.resumeBootstrap;
windowAngular.resumeBootstrap = function() {
windowAngular.resumeBootstrap = originalResumeBootstrap;
windowAngular.resumeBootstrap.apply(this, arguments);
resolve();
};
} else {
resolve();
}
});
Promise.all([this.ng2BootstrapDeferred.promise, ng1BootstrapPromise]).then(([ng1Injector]) => {
angular.element(element).data(controllerKey(NG2_INJECTOR), this.moduleRef.injector);
this.moduleRef.injector.get(NgZone).run(
() => { (upgrade)._bootstrapDone(this.moduleRef, ng1Injector); });
}, onError);
return upgrade;
}
/**
* Allows Angular 1 service to be accessible from Angular 2+.
*
*
* ### Example
*
* ```
* class Login { ... }
* class Server { ... }
*
* @Injectable()
* class Example {
* constructor(@Inject('server') server, login: Login) {
* ...
* }
* }
*
* const module = angular.module('myExample', []);
* module.service('server', Server);
* module.service('login', Login);
*
* const adapter = new UpgradeAdapter(MyNg2Module);
* adapter.upgradeNg1Provider('server');
* adapter.upgradeNg1Provider('login', {asToken: Login});
*
* adapter.bootstrap(document.body, ['myExample']).ready((ref) => {
* const example: Example = ref.ng2Injector.get(Example);
* });
*
* ```
*/
public upgradeNg1Provider(name: string, options?: {asToken: any}) {
const token = options && options.asToken || name;
this.providers.push({
provide: token,
useFactory: (ng1Injector: angular.IInjectorService) => ng1Injector.get(name),
deps: [NG1_INJECTOR]
});
}
/**
* Allows Angular 2+ service to be accessible from Angular 1.
*
*
* ### Example
*
* ```
* class Example {
* }
*
* const adapter = new UpgradeAdapter(MyNg2Module);
*
* const module = angular.module('myExample', []);
* module.factory('example', adapter.downgradeNg2Provider(Example));
*
* adapter.bootstrap(document.body, ['myExample']).ready((ref) => {
* const example: Example = ref.ng1Injector.get('example');
* });
*
* ```
*/
public downgradeNg2Provider(token: any): Function {
const factory = function(injector: Injector) { return injector.get(token); };
(factory).$inject = [NG2_INJECTOR];
return factory;
}
/**
* Declare the Angular 1 upgrade module for this adapter without bootstrapping the whole
* hybrid application.
*
* This method is automatically called by `bootstrap()` and `registerForNg1Tests()`.
*
* @param modules The Angular 1 modules that this upgrade module should depend upon.
* @returns The Angular 1 upgrade module that is declared by this method
*
* ### Example
*
* ```
* const upgradeAdapter = new UpgradeAdapter(MyNg2Module);
* upgradeAdapter.declareNg1Module(['heroApp']);
* ```
*/
private declareNg1Module(modules: string[] = []): angular.IModule {
const delayApplyExps: Function[] = [];
let original$applyFn: Function;
let rootScopePrototype: any;
let rootScope: angular.IRootScopeService;
const componentFactoryRefMap: ComponentFactoryRefMap = {};
const upgradeAdapter = this;
const ng1Module = this.ng1Module = angular.module(this.idPrefix, modules);
const platformRef = platformBrowserDynamic();
this.ngZone = new NgZone({enableLongStackTrace: Zone.hasOwnProperty('longStackTraceZoneSpec')});
this.ng2BootstrapDeferred = new Deferred();
ng1Module.factory(NG2_INJECTOR, () => this.moduleRef.injector.get(Injector))
.constant(NG2_ZONE, this.ngZone)
.constant(NG2_COMPONENT_FACTORY_REF_MAP, componentFactoryRefMap)
.factory(NG2_COMPILER, () => this.moduleRef.injector.get(Compiler))
.config([
'$provide', '$injector',
(provide: angular.IProvideService, ng1Injector: angular.IInjectorService) => {
provide.decorator(NG1_ROOT_SCOPE, [
'$delegate',
function(rootScopeDelegate: angular.IRootScopeService) {
// Capture the root apply so that we can delay first call to $apply until we
// bootstrap Angular 2 and then we replay and restore the $apply.
rootScopePrototype = rootScopeDelegate.constructor.prototype;
if (rootScopePrototype.hasOwnProperty('$apply')) {
original$applyFn = rootScopePrototype.$apply;
rootScopePrototype.$apply = (exp: any) => delayApplyExps.push(exp);
} else {
throw new Error('Failed to find \'$apply\' on \'$rootScope\'!');
}
return rootScope = rootScopeDelegate;
}
]);
if (ng1Injector.has(NG1_TESTABILITY)) {
provide.decorator(NG1_TESTABILITY, [
'$delegate',
function(testabilityDelegate: angular.ITestabilityService) {
const originalWhenStable: Function = testabilityDelegate.whenStable;
// Cannot use arrow function below because we need the context
const newWhenStable = function(callback: Function) {
originalWhenStable.call(this, function() {
const ng2Testability: Testability =
upgradeAdapter.moduleRef.injector.get(Testability);
if (ng2Testability.isStable()) {
callback.apply(this, arguments);
} else {
ng2Testability.whenStable(newWhenStable.bind(this, callback));
}
});
};
testabilityDelegate.whenStable = newWhenStable;
return testabilityDelegate;
}
]);
}
}
]);
ng1Module.run([
'$injector', '$rootScope',
(ng1Injector: angular.IInjectorService, rootScope: angular.IRootScopeService) => {
UpgradeNg1ComponentAdapterBuilder.resolve(this.ng1ComponentsToBeUpgraded, ng1Injector)
.then(() => {
// At this point we have ng1 injector and we have lifted ng1 components into ng2, we
// now can bootstrap ng2.
const DynamicNgUpgradeModule =
NgModule({
providers: [
{provide: NG1_INJECTOR, useFactory: () => ng1Injector},
{provide: NG1_COMPILE, useFactory: () => ng1Injector.get(NG1_COMPILE)},
this.providers
],
imports: [this.ng2AppModule]
}).Class({
constructor: function DynamicNgUpgradeModule() {},
ngDoBootstrap: function() {}
});
(platformRef as any)
._bootstrapModuleWithZone(
DynamicNgUpgradeModule, this.compilerOptions, this.ngZone,
(componentFactories: ComponentFactory[]) => {
componentFactories.forEach((componentFactory) => {
const type: Type = componentFactory.componentType;
if (this.upgradedComponents.indexOf(type) !== -1) {
componentFactoryRefMap[getComponentInfo(type).selector] =
componentFactory;
}
});
})
.then((ref: NgModuleRef) => {
this.moduleRef = ref;
this.ngZone.run(() => {
if (rootScopePrototype) {
rootScopePrototype.$apply = original$applyFn; // restore original $apply
while (delayApplyExps.length) {
rootScope.$apply(delayApplyExps.shift());
}
rootScopePrototype = null;
}
});
})
.then(() => this.ng2BootstrapDeferred.resolve(ng1Injector), onError)
.then(() => {
let subscription =
this.ngZone.onMicrotaskEmpty.subscribe({next: () => rootScope.$digest()});
rootScope.$on('$destroy', () => { subscription.unsubscribe(); });
});
})
.catch((e) => this.ng2BootstrapDeferred.reject(e));
}
]);
return ng1Module;
}
}
interface ComponentFactoryRefMap {
[selector: string]: ComponentFactory;
}
/**
* Synchronous promise-like object to wrap parent injectors,
* to preserve the synchronous nature of Angular 1's $compile.
*/
class ParentInjectorPromise {
private injector: Injector;
private callbacks: ((injector: Injector) => any)[] = [];
constructor(private element: angular.IAugmentedJQuery) {
// store the promise on the element
element.data(controllerKey(NG2_INJECTOR), this);
}
then(callback: (injector: Injector) => any) {
if (this.injector) {
callback(this.injector);
} else {
this.callbacks.push(callback);
}
}
resolve(injector: Injector) {
this.injector = injector;
// reset the element data to point to the real injector
this.element.data(controllerKey(NG2_INJECTOR), injector);
// clean out the element to prevent memory leaks
this.element = null;
// run all the queued callbacks
this.callbacks.forEach((callback) => callback(injector));
this.callbacks.length = 0;
}
}
function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function {
(directiveFactory).$inject =
[NG1_INJECTOR, NG1_COMPILE, NG2_COMPONENT_FACTORY_REF_MAP, NG1_PARSE];
function directiveFactory(
ng1Injector: angular.IInjectorService, ng1Compile: angular.ICompileService,
componentFactoryRefMap: ComponentFactoryRefMap,
parse: angular.IParseService): angular.IDirective {
let idCount = 0;
let dashSelector = info.selector.replace(/[A-Z]/g, char => '-' + char.toLowerCase());
return {
restrict: 'E',
terminal: true,
require: REQUIRE_INJECTOR,
compile: (templateElement: angular.IAugmentedJQuery, templateAttributes: angular.IAttributes,
transclude: angular.ITranscludeFunction) => {
// We might have compile the contents lazily, because this might have been triggered by the
// UpgradeNg1ComponentAdapterBuilder, when the ng2 templates have not been compiled yet
return {
post: (scope: angular.IScope, element: angular.IAugmentedJQuery,
attrs: angular.IAttributes, parentInjector: Injector | ParentInjectorPromise,
transclude: angular.ITranscludeFunction): void => {
let id = idPrefix + (idCount++);
(element[0]).id = id;
let injectorPromise = new ParentInjectorPromise(element);
const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler;
const ngContentSelectors = ng2Compiler.getNgContentSelectors(info.type);
const linkFns = compileProjectedNodes(templateElement, ngContentSelectors);
const componentFactory: ComponentFactory = componentFactoryRefMap[info.selector];
if (!componentFactory)
throw new Error('Expecting ComponentFactory for: ' + info.selector);
element.empty();
let projectableNodes = linkFns.map(link => {
let projectedClone: Node[];
link(scope, (clone: Node[]) => {
projectedClone = clone;
element.append(clone);
});
return projectedClone;
});
parentInjector = parentInjector || ng1Injector.get(NG2_INJECTOR);
if (parentInjector instanceof ParentInjectorPromise) {
parentInjector.then((resolvedInjector: Injector) => downgrade(resolvedInjector));
} else {
downgrade(parentInjector);
}
function downgrade(injector: Injector) {
const facade = new DowngradeNg2ComponentAdapter(
info, element, attrs, scope, injector, parse, componentFactory);
facade.setupInputs();
facade.bootstrapNg2(projectableNodes);
facade.setupOutputs();
facade.registerCleanup();
injectorPromise.resolve(facade.componentRef.injector);
}
}
};
}
};
function compileProjectedNodes(
templateElement: angular.IAugmentedJQuery,
ngContentSelectors: string[]): angular.ILinkFn[] {
if (!ngContentSelectors)
throw new Error('Expecting ngContentSelectors for: ' + info.selector);
// We have to sort the projected content before we compile it, hence the terminal: true
let projectableTemplateNodes =
sortProjectableNodes(ngContentSelectors, templateElement.contents());
return projectableTemplateNodes.map(nodes => ng1Compile(nodes));
}
}
return directiveFactory;
}
/**
* Use `UpgradeAdapterRef` to control a hybrid Angular 1 / Angular 2+ application.
*
* @stable
*/
export class UpgradeAdapterRef {
/* @internal */
private _readyFn: (upgradeAdapterRef?: UpgradeAdapterRef) => void = null;
public ng1RootScope: angular.IRootScopeService = null;
public ng1Injector: angular.IInjectorService = null;
public ng2ModuleRef: NgModuleRef = null;
public ng2Injector: Injector = null;
/* @internal */
private _bootstrapDone(ngModuleRef: NgModuleRef, ng1Injector: angular.IInjectorService) {
this.ng2ModuleRef = ngModuleRef;
this.ng2Injector = ngModuleRef.injector;
this.ng1Injector = ng1Injector;
this.ng1RootScope = ng1Injector.get(NG1_ROOT_SCOPE);
this._readyFn && this._readyFn(this);
}
/**
* Register a callback function which is notified upon successful hybrid Angular 1 / Angular 2+
* application has been bootstrapped.
*
* The `ready` callback function is invoked inside the Angular 2+ zone, therefore it does not
* require a call to `$apply()`.
*/
public ready(fn: (upgradeAdapterRef?: UpgradeAdapterRef) => void) { this._readyFn = fn; }
/**
* Dispose of running hybrid Angular 1 / Angular 2+ application.
*/
public dispose() {
this.ng1Injector.get(NG1_ROOT_SCOPE).$destroy();
this.ng2ModuleRef.destroy();
}
}
/**
* Sort a set of DOM nodes that into groups based on the given content selectors
*/
export function sortProjectableNodes(ngContentSelectors: string[], childNodes: Node[]): Node[][] {
let projectableNodes: Node[][] = [];
let matcher = new SelectorMatcher();
let wildcardNgContentIndex: number;
for (let i = 0, ii = ngContentSelectors.length; i < ii; i++) {
projectableNodes[i] = [];
if (ngContentSelectors[i] === '*') {
wildcardNgContentIndex = i;
} else {
matcher.addSelectables(CssSelector.parse(ngContentSelectors[i]), i);
}
}
for (let node of childNodes) {
let ngContentIndices: number[] = [];
let selector =
createElementCssSelector(node.nodeName.toLowerCase(), getAttributesAsArray(node));
matcher.match(
selector, (selector, ngContentIndex) => { ngContentIndices.push(ngContentIndex); });
ngContentIndices.sort();
if (wildcardNgContentIndex !== undefined) {
ngContentIndices.push(wildcardNgContentIndex);
}
if (ngContentIndices.length > 0) {
projectableNodes[ngContentIndices[0]].push(node);
}
}
return projectableNodes;
}