diff --git a/modules/@angular/upgrade/src/upgrade_adapter.ts b/modules/@angular/upgrade/src/upgrade_adapter.ts index d4f4b2de176..ac305581289 100644 --- a/modules/@angular/upgrade/src/upgrade_adapter.ts +++ b/modules/@angular/upgrade/src/upgrade_adapter.ts @@ -656,67 +656,59 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function restrict: 'E', terminal: true, require: REQUIRE_INJECTOR, - compile: (templateElement: angular.IAugmentedJQuery, templateAttributes: angular.IAttributes, - transclude: angular.ITranscludeFunction) => { + link: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes, + parentInjector: Injector | ParentInjectorPromise): void => { // 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); + let id = idPrefix + (idCount++); + (element[0]).id = id; - const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler; - const ngContentSelectors = ng2Compiler.getNgContentSelectors(info.type); - const linkFns = compileProjectedNodes(templateElement, ngContentSelectors); + let injectorPromise = new ParentInjectorPromise(element); - const componentFactory: ComponentFactory = componentFactoryRefMap[info.selector]; - if (!componentFactory) - throw new Error('Expecting ComponentFactory for: ' + info.selector); + const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler; + const ngContentSelectors = ng2Compiler.getNgContentSelectors(info.type); + const linkFns = compileProjectedNodes(element, ngContentSelectors); - element.empty(); - let projectableNodes = linkFns.map(link => { - let projectedClone: Node[]; - link(scope, (clone: Node[]) => { - projectedClone = clone; - element.append(clone); - }); - return projectedClone; - }); + const componentFactory: ComponentFactory = componentFactoryRefMap[info.selector]; + if (!componentFactory) throw new Error('Expecting ComponentFactory for: ' + info.selector); - parentInjector = parentInjector || ng1Injector.get(NG2_INJECTOR); + element.empty(); + let projectableNodes = linkFns.map(link => { + let projectedClone: Node[]; + link(scope, (clone: Node[]) => { + projectedClone = clone; + element.append(clone); + }); + return projectedClone; + }); - if (parentInjector instanceof ParentInjectorPromise) { - parentInjector.then((resolvedInjector: Injector) => downgrade(resolvedInjector)); - } else { - downgrade(parentInjector); - } + parentInjector = parentInjector || ng1Injector.get(NG2_INJECTOR); - 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); - } - } - }; + 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[] { + element: 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()); + let projectableTemplateNodes = sortProjectableNodes(ngContentSelectors, element.contents()); return projectableTemplateNodes.map(nodes => ng1Compile(nodes)); } } diff --git a/modules/@angular/upgrade/test/aot/integration/content_projection_spec.ts b/modules/@angular/upgrade/test/aot/integration/content_projection_spec.ts index 7c48ac8b46f..e9b5adbf01a 100644 --- a/modules/@angular/upgrade/test/aot/integration/content_projection_spec.ts +++ b/modules/@angular/upgrade/test/aot/integration/content_projection_spec.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, ElementRef, Injector, NgModule, destroyPlatform} from '@angular/core'; +import {Component, Directive, ElementRef, Injector, Input, NgModule, destroyPlatform} from '@angular/core'; import {async} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import * as angular from '@angular/upgrade/src/angular_js'; import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; -import {bootstrap, html} from '../test_helpers'; +import {bootstrap, html, multiTrim} from '../test_helpers'; export function main() { describe('content projection', () => { @@ -52,6 +52,44 @@ export function main() { }); })); + it('should correctly project structural directives', async(() => { + @Component({selector: 'ng2', template: 'ng2-{{ itemId }}()'}) + class Ng2Component { + @Input() itemId: string; + } + + @NgModule({ + imports: [BrowserModule, UpgradeModule], + declarations: [Ng2Component], + entryComponents: [Ng2Component] + }) + class Ng2Module { + ngDoBootstrap() {} + } + + const ng1Module = + angular.module('ng1', []) + .directive( + 'ng2', downgradeComponent({component: Ng2Component, inputs: ['itemId']})) + .run(($rootScope: angular.IRootScopeService) => { + $rootScope['items'] = [ + {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, + {id: 'c', subitems: [7, 8, 9]} + ]; + }); + + const element = html(` + +
{{ subitem }}
+
+ `); + + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + expect(multiTrim(document.body.textContent)) + .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); + }); + })); + it('should instantiate ng1 in ng2 template and project content', async(() => { @Component({ diff --git a/modules/@angular/upgrade/test/upgrade_spec.ts b/modules/@angular/upgrade/test/upgrade_spec.ts index 9fe7e66b8d7..174c593d29f 100644 --- a/modules/@angular/upgrade/test/upgrade_spec.ts +++ b/modules/@angular/upgrade/test/upgrade_spec.ts @@ -491,6 +491,39 @@ export function main() { }); })); + it('should correctly project structural directives', async(() => { + @Component({selector: 'ng2', template: 'ng2-{{ itemId }}()'}) + class Ng2Component { + @Input() itemId: string; + } + + @NgModule({imports: [BrowserModule], declarations: [Ng2Component]}) + class Ng2Module { + } + + const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); + const ng1Module = angular.module('ng1', []) + .directive('ng2', adapter.downgradeNg2Component(Ng2Component)) + .run(($rootScope: angular.IRootScopeService) => { + $rootScope['items'] = [ + {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, + {id: 'c', subitems: [7, 8, 9]} + ]; + }); + + const element = html(` + +
{{ subitem }}
+
+ `); + + adapter.bootstrap(element, [ng1Module.name]).ready(ref => { + expect(multiTrim(document.body.textContent)) + .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); + ref.dispose(); + }); + })); + it('should allow attribute selectors for components in ng2', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module)); const ng1Module = angular.module('myExample', []); @@ -1901,14 +1934,17 @@ function multiTrim(text: string): string { } function html(html: string): Element { + // Don't return `body` itself, because using it as a `$rootElement` for ng1 + // will attach `$injector` to it and that will affect subsequent tests. const body = document.body; - body.innerHTML = html; + body.innerHTML = `
${html.trim()}
`; + const div = document.body.firstChild as Element; - if (body.childNodes.length == 1 && body.firstChild instanceof HTMLElement) { - return body.firstChild; + if (div.childNodes.length === 1 && div.firstChild instanceof HTMLElement) { + return div.firstChild; } - return body; + return div; } function nodes(html: string) {